数据结构之排序算法

  这里的排序方法是发生在内存中,因此是内部排序。

1.插入排序-直接插入排序

基本思想:将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

稳定性:如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

复杂度:平均复杂度:O(n^2)。

  最优复杂度:当输入数组就是排好序的时候,复杂度为O(n)。

  最差复杂度:当输入数组为倒序时,复杂度为O(n^2)。

  因此插入排序比较适合“少量元素的数组”。

#define _CRT_SECURE_NO_DEPRECATE
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 20
void InsertSort(int s[], int n)//插入排序,稳定O(n^2)
{
	int j,temp;
	for (int i = 1; i < n - 1; i++)
	{
		temp = s[i];
		//将此元素temp与前面有序状态的序列往前依次比较,temp小,则将大的元素往后移动,知道找到合适位置
		for (j = i - 1; j >= 0 && s[j]>temp; j--)
			s[j + 1] = s[j];
		s[j + 1] = temp;//正确位置j+1
	}
}

2.插入排序-希尔排序

基本思想:先将整个待排序的记录序列利用跨度dk分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

稳定性:不稳定。

复杂度:平均复杂度O(n^1.3)(课本要求平均、最优、最差都为(n^1.3))。

//希尔排序就是插入排序的改进,只不过将插入排序的每次比较幅度1变成了dk(dk也是最终变化到1的)
void ShellSort(int s[], int n)//希尔排序,不稳定 O(n^3/2)
{
	int i,j, dk, temp;
	dk = n / 2;
	while (dk >= 1)
	{
		for (i = dk ;i < n ;i++)
		{
			temp = s[i];
			for (j = i - dk; j >= 0 && temp < s[j]; j -= dk)
				s[j + dk] = s[j];
			s[j + dk] = temp;
		}
		dk /= 2;
	}
}

3.交换排序-冒泡排序

基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

稳定性:稳定。

时间复杂度:平均复杂度:O(n^2)。

  最优复杂度:当输入数组就是排好序的时候,复杂度为O(n)。

  最差复杂度:当输入数组为倒序时,复杂度为O(n^2)。

void BubbleSort(int  s[],int n)//冒泡排序,稳定O(n^2)
{
	bool change;//改进的冒泡排序,添加bool型变量,当某一趟检测已经有序则结束
	int temp;
	int count1=0, count2=0;//计数变量
	for (int i = 1; i < n; i++)//双循环来进行冒泡排序
	{
		change = false;//此趟开始初始化change变量
		for (int j = 0; j < n - i; j++)
		{
			count1++;
			if (s[j]>s[j + 1])
			{
				count2++;
				temp = s[j];
				s[j] = s[j + 1];
				s[j + 1] = temp;
				change = true;//有交换了,此时还未成有序状态,设置change变量
			}
		}
		if (change == false)//如果此趟结束后还未有过交换,即已是有序状态
			break;
	}
	printf("总共比较%3d次,总共交换%3d次\n", count1, count2);
}

4.交换排序-快速排序

基本思想:

  1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

  2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。

  3)此时基准元素在其排好序后的正确位置

  4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

稳定性:不稳定。

时间复杂度:平均复杂度:O(nlogn)。

  最优复杂度:当输入数组就是排好序的时候,复杂度为O(nlogn)。

  最差复杂度:当输入数组为倒序时,复杂度为O(n^2)。

辅助空间:O(nlogn)。

void Quick(int s[], int low, int high)
{
	int point,i,j;
	if (low < high)
	{
		point = s[low];//以第一个元素来找其应该所在的位置
		i = low ;
		j = high;
		while (i < j)
		{
			while (i < j&&s[j] >= point)//右边小的放左边
				j--;
			s[i] = s[j];
			while (i<j&&s[i]<=point)//左边大的放右边
				i++;
			s[j] = s[i];
		}
		s[i] = point;
		Quick(s, low, i - 1);//递归左边
		Quick(s, i + 1, high);//递归右边
	}
}

  为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码大小顺序居中的调整为支点记录,当规模小时采用直接插入排序。

void swap(int s[], int low,int high)//交换函数
{
	int t;
	t = s[low];
	s[low] = s[high];
	s[high] = t;
}
void Quick1(int s[], int low, int high)//优化,选取三数取中法避免效率最低的情况
{
	int point, i, j;
	int m = (low + high) / 2;
	if (s[low] > s[high])
	{
		swap(s, low, high);
	}
	if (s[m] > s[high])
	{
		swap(s, m, high);
	}
	if (s[m] > s[low])
	{
		swap(s, m, low);
	}
	if (high-low>6)//大规模就用快速排序
	{
		point = s[low];
		i = low;
		j = high;
		while (i < j)
		{
			while (i < j&&s[j] >= point)
				j--;
			s[i] = s[j];
			while (i<j&&s[i] <= point)
				i++;
			s[j] = s[i];
		}
		s[i] = point;
		Quick(s, low, i - 1);
		Quick(s, i + 1, high);
	}
	else
	{
		InsertSort(s+low, high-low+1);//规模小数组调用插入排序
	}
}
void QuickSort(int s[], int n)//冒泡排序升级版 不稳定O(nlogn)
{
	Quick1(s, 0, n - 1);
}

5.选择排序-简单选择排序 

基本思想:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

稳定性:不稳定。

复杂度:平均、最优、最差复杂度都为O(n^2)。

void SelectSort(int s[], int n)//选择排序,不稳定O(n^2)
{
	int count1 = 0, count2 = 0;
	int t,temp;
	for (int i = 0; i < n-1; i++)
	{
		t = i;
		for (int j = i + 1; j < n; j++)//通过比较用 t 记录最小元素的编号
		{
			count1++;
			if (s[t] > s[j])
				t = j;
		}
		//将此趟选出的最小元素和其在有序状态下应该处在的位置i的元素交换
		temp = s[t];
		s[t] = s[i];
		s[i] = temp;
		count2++;
	}
	printf("总共比较%3d次,总共交换%3d次\n", count1, count2);
}

6.选择排序-堆排序  

基本思想:根据堆的定义,最大堆堆顶为最大值,堆顶元素与最后一个元素交换,和选择排序类似,然后将之前n-1个元素再继续构造堆,这样来排序,为了简单起见,利用二叉树根节点与子节点的关系,从数组编号为1开始排序。

稳定性:不稳定。

复杂度:平均、最优、最差复杂度都为O(nlogn)。

void HeapAdjust(int s[], int a, int n)
{
	int i,temp;
	temp = s[a];
	for (i = a * 2; i <= n; i *= 2)//一直找后代,知道符合堆原则,不能只是往下比一层
	{
		if (i < n&& s[i] < s[i + 1])//如果有右孩子且右孩子比左孩子大,则用根元素和右孩子比
			i++;
		if (temp >= s[i])//因为是从底向上来调整堆,当某一层符合了,下面一定符合堆的原则
			break;
		s[a] = s[i];//最后确定的temp的位置
		a = i;
	}
	s[a] = temp;//一直遍历找到适合的位置
}
void HeapSort(int s[], int n)//堆排序,不稳定
{
	int i = 0;
	for (i = n / 2; i >= 1; i--)//先找最左下的度不为0的节点也就是编号为n/2(向下取整)
	{
		HeapAdjust(s, i, n);
	}
	for (i = n; i > 1; i--)//将调整后的最大堆的堆顶与最后一个元素交换,类似选择排序的思想
	{
		swap(s, 1, i);
		HeapAdjust(s, 1,i-1);
	}
}

7.归并排序

基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

稳定性:稳定。

复杂度:平均、最优、最差复杂度都为O(nlogn)。

辅助空间:O(n)。

给出自顶向下和自底向上两种算法:

void Merging(int *list1, int list1_size, int *list2, int list2_size)
{//此函数就是经典的两个有序数组合并为一个有序数组
	int i, j, k;
	int temp[MAXSIZE];
	i = j = k = 0;
	while (i < list1_size && j < list2_size)
	{
		if (list1[i] < list2[j])
			temp[k++] = list1[i++];
		else
			temp[k++] = list2[j++];
	}
	while (i < list1_size)
		temp[k++] = list1[i++];
	while (j < list2_size)
		temp[k++] = list2[j++];
	for (int m = 0; m < (list1_size + list2_size); m++)//两个数组合并后放到list1中
		list1[m] = temp[m];
}
void MergeSortTop(int s[], int n)//二路归并排序递归,自上而下,稳定,O(nlogn),需要空间O(n)
{
	if (n > 1){
	int *list1 = s;
	int list1_size = n / 2;
	int *list2 = s + n / 2;
	int list2_size = n - list1_size;
	//一分二,二分四,直到分到大小1为止也就是自上而下
	MergeSortTop(list1, list1_size);//左归并
	MergeSortTop(list2, list2_size);//右归并
	Merging(list1, list1_size, list2, list2_size);//已成的两个有序的左右合并
	}
}
void MergePart(int *s, int n, int length)//以间隔为length的步长来分别归并
{
	int i = 0;//从编号为0的数组元素开始排序
	for (; i + 2 * length<n - 1; i += 2 * length)
	{
		Merging(s+i, length,s+i + length, length);
	}
	if (i + length <= n - 1)
		Merging(s+i, length,s+i+length,n-i-length );//尚有两个子文件,其中后一个长度小于length,归并最后两个子文件  注意:若i≤n-1且i+length-1≥n-1时,则剩余一个子文件轮空,无须归并
}
void MergeSortBottom(int s[], int n)//二路归并迭代,自下而上
{
	for (int length = 1; length<n; length *= 2)
		MergePart(s, n, length);
}

8.测试函数:

int main()
{
	int s[] = { -1,2, 4, 5, 3, 7, 8, 0, 6, 10, 9, 1 };
	printf("排序初始序列为(有的排序方法从编号为1的元素开始,则去掉首元素):\n");
	for (int i = 0; i < sizeof(s) / 4; i++)
		printf("%3d", s[i]);
	printf("\n请输入想要进行的排序:\n1代表冒泡排序\n2代表选择排序\n3代表插入排序\n4代表希尔排序\n5代表归并排序自顶而下\n6代表归并排序自下而上\n7代表快速排序\n8代表堆排序\n");
	int choice = 0;
	scanf("%d", &choice);
	switch (choice)
	{
		case 1:
			BubbleSort(s, sizeof(s) / 4);
			printf("冒泡排序结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 2:
			SelectSort(s, sizeof(s) / 4);
			printf("选择排序结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 3:
			InsertSort(s, sizeof(s) / 4);
			printf("插入排序结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 4:
			ShellSort(s, sizeof(s) / 4);
			printf("希尔排序结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 5:
			MergeSortTop(s, sizeof(s) / 4);
			printf("归并排序自顶而下结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 6:
			MergeSortBottom(s, sizeof(s) / 4);
			printf("归并排序自底而上结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 7:
			QuickSort(s, sizeof(s) / 4);
			printf("快速排序结果:\n");
			for (int i = 0; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		case 8:
			HeapSort(s, (sizeof(s) / 4) - 1);
			printf("堆排序自顶而下结果:\n");
			for (int i = 1; i < sizeof(s) / 4; i++)
				printf("%3d", s[i]);
			printf("\n");
			break;
		default:
			break;
	}
	system("pause");
}

9.算法稳定性,时间复杂度和空间复杂度总结

时间: 2024-11-05 17:34:11

数据结构之排序算法的相关文章

数据结构-各类排序算法总结[结局]

各类排序算法总结 五.分配类排序->基数排序: 基数排序是一种借助于多关键码排序的思想,是将单关键码按基数分成"多关键码"进行排序的方法.基数排序属于"低位优先"排序法,通过反复进行分配与收集操作完成排序. 对于数字型或字符型的单关键字,可以看成是由多个数位或多个字符构成的多关键字, 此时可以采用这种"分配-收集"的办法进行排序,称作基数排序法.其好处是不需要进行关键字间的比较. 例如:对下列这组关键字{278, 109, 063, 930

数据结构-各类排序算法总结[续]

各类排序算法总结 三.交换类排序[接上] 2.快速排序 快速排序是通过比较关键码.交换记录,以某个记录为界(该记录称为支点),将待排序列分成两部分.其中,一部分所有记录的关键码大于等于支点记录的关键码,另一部分所有记录的关键码小于支点记录的关键码.我们将待排序列按关键码以支点记录分成两部分的过程,称为一次划分.对各部分不断划分,直到整个序列按关键码有序. 如果每次划分对一个元素定位后,该元素的左侧子序列与右侧子序列的长度相同,则下一步将是对两个长度减半的子序列进行排序,这是最理想的情况! [算法

数据结构—各类‘排序算法’实现(上)

数据结构中的排序算法分为比较排序,非比较排序.比较排序有插入排序.选择排序.交换排序.归并排序,非比较排序有计数排序.基数排序.下面是排序的具体分类: 1.直接排序 主要思想:使用两个指针,让一个指针从开始,另一个指针指向前一个指针的+1位置,两个数据进行比较 void InsertSort(int* a, size_t size) {      assert(a);      for (size_t i = 0; i < size - 1; i++)      {           int 

数据结构-各类排序算法总结

各类排序算法总结 一. 排序的基本概念 排序(Sorting)是计算机程序设计中的一种重要操作,其功能是对一个数据元素集合或序列重新排列成一个按数据元素某个项值有序的序列. 有 n 个记录的序列{R1,R2,-,Rn},其相应关键字的序列是{K1,K2,-,Kn},相应的下标序列为1,2,-,n.通过排序,要求找出当前下标序列1,2,-, n 的一种排列p1,p2, -,pn,使得相应关键字满足如下的非递减(或非递增)关系,即:Kp1≤Kp2≤-≤Kpn,这样就得到一个按关键字有序的记录序列{R

【数据结构】——排序算法——3.1、选择排序

      [数据结构]--排序算法--3.1.选择排序 一.先上维基的图: 分类 排序算法 数据结构 数组 最差时间复杂度 О(n2) 最优时间复杂度 О(n2) 平均时间复杂度 О(n2) 最差空间复杂度 О(n) total, O(1)auxiliary 二.描述: 选择算法算是最直观的一个了.每次在队列里抽取一个极大(或极小)值进行排列.每次都需要遍历未被抽取的元素队列. 三.Java程序: static void selection_sort(int[] unsorted) { for

【数据结构】——排序算法——1.1、直接插入排序

插入算法很多,无论是在内功修炼,各种笔试面试都是相当有用的.接下来,将陆续将各种排序算法进行练习: 主要分为以下几个部分(其他后面学习补充): 一.插入类排序:1.直接插入排序:2.折半插入排序:3.希尔shell排序: 二.交换类排序:1.冒泡排序 :2.快速排序: 三.选择类排序:1.简单选择: 2.堆排序: 本人多使用Java--开始吧! 首先推荐1.维基百科<排序算法>词条,图文并茂,很形象!2.学习博文<维基百科上的算法和数据结构链接很强大>,资料很多,保存学习! [数据

复习数据结构:排序算法(一)——插入排序

从这一篇开始,计划复习一下数据结构的基本知识.一来是为了开年之后的找工作,二来是为了想提升自己的编程能力.由于这些数据结构知识点,之前都学习过,这里我们就提炼出每个知识点的核心,以及代码实现. 这篇先说排序算法中的插入排序. 插入排序是一种稳定排序算法,属于内排序.适合少量数据量的排序. 当输入数组已经排好序时,插入排序需要O(n),快排需要O(n^2). 当输入数组倒序排列时,插入排序时复为:O(n^2). 平均时间复杂度:O(n^2). 代码实现如下: #include<iostream>

Java中的数据结构及排序算法

(明天补充) 主要是3种接口:List Set Map List:ArrayList,LinkedList:顺序表ArrayList,链表LinkedList,堆栈和队列可以使用LinkedList模拟 Set:HashSet没有重复记录的集合 Map:HashMap就是哈希表 Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └WeakHashMap 数据结构参考链接

数据结构——各排序算法的比较

1.从时间复杂度比较  从平均时间复杂度来考虑,直接插入排序.冒泡排序.直接选择排序是三种简单的排序方法,时间复杂度都为O(n2),而快速排序.堆排序.二路归并排序的时间复杂度都为O(nlog2n),希尔排序的复杂度介于这两者之间.若从最好的时间复杂度考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它的最好情形同平均情形相同.若从最坏的时间复杂度考虑,则快速排序的为O(n2),直接插入排序.冒泡排序.希尔排序同平均情形相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情形对直接

数据结构基础 排序算法(一) 概念篇

本辑将会对笔试面试最常涉及到的12种排序算法(包括插入排序.二分插入排序.希尔排序.选择排序.冒泡排序.鸡尾酒排序.快速排序.堆排序.归并排序.桶排序.计数排序和基数排序)进行详解.每一种算法都有基本介绍.算法原理分析.图解演示.算法代码.笔试面试重点分析.笔试面试题等板块. 一.插入排序 1)算法简介 插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法.它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入.插入排序在实现上,通常