《排序算法全解析:从基础到优化,一文吃透八大排序!》

一、为什么我们需要读懂排序--不止于"排顺序"的核心价值

排序是计算机科学中最基础也是最核心的操作之一,看似只是将数据排成有序,实则贯穿我们生活中的每个角落,拼多多上的价格排序,搜索引擎上的权重排序,甚至是最常见的手机相册上的时间排序背后都依赖着排序算法的支撑

但大多数初学者往往会陷入"死记代码"的误区,而忽略了排序的本质--通过高效的比较与交换,降低数据的无序度

本文将从排序算法的设计思路,底层原理,时间复杂度以及适用场景来帮你彻底搞懂排序

核心对比总表:

二、必学排序算法:底层逻辑+代码实现(附上详细注释)

注:以下排序默认是排升序

冒泡排序

核心思想:

通过重复的遍历数组,每次比较两两相邻的数据,若顺序正确就不交换,顺序错误就交换,每次遍历就完成一个数据的排序(最后一个数据成为最大/最小的数据)

特点:

稳定排序(相等元素顺序不变),时间复杂度O(N^2),空间复杂度O(1)

适用场景:

无实践意义,仅有教学意义

时间复杂度:

考虑最坏的情况:逆序

假设有n个数据,第一个数据最大,通过两两交换,要交换n-1次才能交换到最后一个,此时次大的数据被交换到第一个,最后一个数据已经确定,所以要交换n-2次才能交换到倒数第二个,依次类推,实际上是一个等差数列(n-1+n-2+n-3+......+1) = (n(n-1))/2,故时间复杂度为O(N^2)

代码实现:

点击查看代码

复制代码
void Swap(int* x, int* y) {
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

void BubbleSort(int* a, int size) {
	for (int j = 0; j < size; j++) {
		int Flag = 0;
		for (int i = 0; i < size - j - 1; i++) {
			if (a[i] > a[i + 1]) {
				Flag = 1;
				Swap(&a[i], &a[i + 1]);
			}
		}
                //此时一次遍历没有两两交换,说明已经有序了
		if (Flag == 0) {
			break;
		}
	}
}

堆排序

核心思想:

先用向下调整法建大堆,此时堆顶元素是最大的数据,然后与最后一个元素交换,最后一个元素变成了最大的数据,注意size--,再向下调整法建堆,寻找次大的元素,继续交换,依次类推......

特点:

不稳定排序(会进行跨位置交换,可能打乱原相等数据的相对位置),时间复杂度为O(N*logN),空间复杂度为O(1)

适用场景:

数据量较大且不需要排序结果稳定的场景

可以解决TopK问题
点击查看代码

复制代码
void GiveFile() {
      //建立文件
	const char* filepath = "randomdata.txt";
	FILE* fin = fopen(filepath, "w");
	if (fin == NULL) {
		perror("fopen():error");
		return ;
	}
	int size = 100000;
	for (int i = 0; i < size; i++) {
		int random = rand() + i;
		fprintf(fin, "%d\n", random);
	}
	fclose(fin);
}

void FindTopK() {
	const char* filepath = "randomdata.txt";
	FILE* fout = fopen(filepath, "r");
	if (fout == NULL) {
		perror("fopen():error");
		return;
	}
	int k = 0;
	printf("请输入k:\n");
	scanf("%d", &k);
	//将前k个数放进了数组中
	int* a = (int*)malloc(k * sizeof(int));
	if (a == NULL) {
		perror("malloc fail");
		return;
	}
	for (int i = 0; i < k; i++) {
		fscanf(fout, "%d", &a[i]);
	}
	//调成小堆
	for (int i = (k - 2) / 2; i >= 0; i--) {
		AdjustDown(a, k, i);
	}
	//将文件中的数据依次与堆顶数据比较
	int tmp = 0;
	while (fscanf(fout, "%d", &tmp)==1) {
		if (tmp > a[0]) {
			a[0] = tmp;
			AdjustDown(a, k, 0);
		}
	}
	//这个时候已经获得了前k大的数,但不分先后
	int tmpk = k;
	//最后一次插入后整体不一定满足小堆
	AdjustDown(a, k, 0);
	//这个时候已经满足小堆,用堆排序
	for (int i = 0; i < tmpk;i++) {
		Swap(&a[0], &a[k - 1]);
		k--;
		AdjustDown(a, k, 0);
	}
	for (int i = 0; i < tmpk; i++) {
		printf("%d ", a[i]);
	}
	fclose(fout);
}

//找出10万个数中前k大的数
int main() {
	srand((unsigned int)time(NULL));
	//方法一:建立大堆,取堆顶的数据,然后pop,用向下调整算法使堆顶数据成为第二大的,继续取......
	//这样的问题是空间复杂度为O(N)

	//方法二:建立前k个数的小堆,先把前k个数放进去,然后将剩下的数据依次与堆顶数据比较,如果比堆顶数据大,进栈,然后用向下调整法调成小堆
	//最后堆里的k个元素就是前k大的数(不分顺序)
	//还可以再用一次向下调整法调整至小堆,再用堆排序
	GiveFile();
	FindTopK();
	return 0;
}

时间复杂度:

向下调整建堆:

排序:

代码实现:

点击查看代码

复制代码
void AdjustDwon(int* a, int size, int parent) {
	int morechild = 2 * parent + 1;
	while (morechild < size) {
		//找到较大子节点
		if (morechild + 1 < size && a[morechild] < a[morechild + 1]) {
			morechild++;
		}
		if (a[morechild] > a[parent]) {
			Swap(&a[morechild], &a[parent]);
			parent = morechild;
			morechild = 2 * parent + 1;
		}
		else {
			break;
		}
	}
}

//时间复杂度为O(N*logN)
void HeapSort(int* a, int size) {
	//先用向下调整法建大堆
	int parent = (size - 2) / 2;
	while(parent >= 0) {
		AdjustDwon(a, size, parent);
		parent--;
	}
	int tmpsize = size;
	for (int i = 0; i < tmpsize; i++) {
		Swap(&a[0], &a[size - 1]);
		size--;
		AdjustDwon(a, size, 0);
	}
}

插入排序

核心思想:

end前面的数据已经实现了有序,新插入的数据需要与前面的数据依次比较,如果新数据较小,那么原来的数据往前挪,直到新数据比原数据大为止,从而实现了有序

特点:

稳定排序,时间复杂度为O(N2),适应性较好(实际上往往介于O(N2)与O(N)之间),空间复杂度为O(1)

适用场景:

小规模数据排序(如快排的小区间优化),近似有序数据排序

时间复杂度:

代码实现:

点击查看代码

复制代码
void InsertSort(int* a, int size) {
	//end前面的已经是有序了,然后将end+1与前面的依次比较
	for (int i = 0; i < size - 1; i++) {
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0) {
			if (tmp < a[end]) {
				a[end + 1] = a[end];
				end--;
			}
			else {
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

选择排序

核心思想:

多次遍历,找到最大值与最小值,将最大值与末尾值交换,将最小值与开头值交换

特点:

不稳定排序(涉及跨位置交换),时间复杂度为O(N^2),空间复杂度为O(1)

适用场景:

无实际意义,仅有教学意义

时间复杂度:

适用场景:

适应性差,无法判断是否有序,时间复杂度一般都在O(N^2),仅具有教学意义

代码实现:

点击查看代码

复制代码
void SelectSort(int* a, int size) {
	int begin = 0;
	int end = size - 1;
	//将最大与最小分别放在两边
	while (begin <= end) {
		int mini = begin;
		int maxi = begin;
		for (int i = begin + 1; i <= end; i++) {//应该是<=
			if (a[i] > a[maxi]) {
				maxi = i;
			}
			if (a[i] < a[mini]) {
				mini = i;
			}
		}
		Swap(&a[begin], &a[mini]);//应该用交换函数,否则会覆盖
		if (begin == maxi)
			maxi = mini;//这里begin可能与maxi相同,mini的值就会被交换走,
		//这里即使end==mini也没关系,因为不影响后序的排序
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

希尔排序

核心思想:

其底层仍然是插入排序,但插入排序有一个缺点:在处理逆序或接近逆序的时候时间复杂度偏高,所以在插入排序前先进行预排序,使大的数尽量排在后面

特点:

不稳定排序,时间复杂度约为O(N^(1.3)),空间复杂度为O(1)

适用场景:

中等规模数据的高效排序,且对内存消耗要求较低

时间复杂度:

希尔排序的时间复杂度受增量序列的影响较大,选择合适的增量序列可以显著提高排序效率。作者水平有限,无法推导

代码实现:

点击查看代码

复制代码
void ShellSort(int* a, int size) {
	//先预排序,把大的数据往后边放,把小的数据往前挪
	int gap = size;//当gap=1时就跟插入排序是一样的
	while (gap>1) {
		gap = gap / 3 + 1;//加1的目的是保证最后一次gap=1,从而实现插入排序
		for (int j = 0; j < gap; j++) {
			for (int i = j; i < size - gap; i += gap) {
				int end = i;
				int tmp = a[end + gap];
				while (end >= 0) {
					if (a[end] > tmp) {
						a[end + gap] = a[end];
						end -= gap;
					}
					else {
						break;
					}
				}
				a[end + gap] = tmp;
			}//这层循环是控制以gap为一组的交换
		}//循环gap次,把所有的组都插入交换一次
	}//多次以不同的gap值预排序,最后gap==1,插入排序
}

快排

核心思想:

单趟快排(代码实现):
点击查看代码

复制代码
//霍尔的单趟快排
int QuickSort1(int* a, int left, int right) {
	int mid = GetMid(a, left, right);
	int key = left;
	Swap(&a[key], &a[mid]);
	int begin = left;
	int end = right;
	while (begin < end) {
		while (begin < end && a[end] >= a[key]) {
			end--;
		}
		while (begin < end && a[begin] <= a[key]) {
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[key], &a[begin]);
	return begin;
}

//双指针法的单趟快排
int QuickSort2(int* a, int left,int right) {
	int key = left;
	int prev = left;
	int cur = left + 1;
    //cur指针是为了找比key小的数,找到了就先++prev然后与prev交换数据,cur++,没找到就cur++,prev不动    
	while(cur<=right) {
		if (a[cur] < a[key] && ++prev != cur) {
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[key], &a[prev]);
	return prev;
}

特点:

不稳定排序,时间复杂度为O(N*logN),空间复杂度为O(1)

适用场景:

内存中数据的高效排序,且对排序稳定性没有要求

时间复杂度:

注:默认情况是选最左边的数字作为key

最优情况:每次选key都选到中间数字,即每次等长划分区间

此时就如同树一般,递归深度为logN,每层都需要遍历一遍,时间为O(N),故时间复杂度为O(N*logN)

最坏情况:每次都选到最大或者最小的数作为key,每次划分会得到长度为n-1和0的子数组,此时递归的层数为N层,每层时间为O(N),故时间复杂度为O(N^2)

由此可见:选key是重中之重,下面介绍两种方式优化选key的方式

1、随机数选key:利用随机数选key,极大概率上避免了key选到极端值(最大或最小)的情况

2、三输取中法选key:从最左边和最右边和中间选一个中间的数,这样永远不可能选到最小或最大的数了

代码实现:
点击查看代码

复制代码
int GetMid(int*a,int left, int right) {
	int mid = (left + right) / 2;
	if (a[left] > a[right]) {
		if (a[right] > a[mid])
			return right;
		else if (a[mid] > a[left])
			return left;
		else
			return mid;
	}
	else {
		if (a[left] > a[mid])
			return left;
		else if (a[mid] > a[right])
			return right;
		else
			return mid;
	}
}

代码实现:

递归版:
点击查看代码

复制代码
void QuickSort(int* a, int left, int right) {
	if (left >= right)
		return;
	//小区间优化,如果最后只剩几个值了,如果继续递归就会比较浪费时间了。这个时候可以调用其他函数来排这几个数
	if (right - left + 1 < 10) {
		InsertSort(a + left, right - left + 1);//这里是对a+left以后的数据进行排序,所以要加left,注意
	}
	else {
		int mid = GetMid(a, left, right);//这是为了处理顺序的情况,如果使顺序的话,它会不断拆分成一个,然后回归,所以要使key不能最大也不能最小
		int key = mid;
		Swap(&a[key], &a[mid]);
		int begin = left;
		int end = right;
		while (begin < end) {
			while (begin < end && a[end] >= a[key]) {
				end--;
			}
			while (begin < end && a[begin] <= a[key]) {
				begin++;
			}
			Swap(&a[begin], &a[end]);
		}
		Swap(&a[begin], &a[key]);
		//分为三个区间: [key,begin-1] begin == end [end+1,right] 
		QuickSort(a, left, begin - 1);
		QuickSort(a, end + 1, right);
	}
}

非递归版:

用栈来模拟递归的实现:利用栈的特性(后进先出)来保存左右区间
点击查看代码

复制代码
#include "Stack.h"
//栈的实现详情请看上一期
void QuickSortNonR(int* a, int left, int right) {
	//用栈来保存区间
	ST st;
	STinit(&st);
	STpush(&st, right);
	STpush(&st, left);
        //栈中没有区间了才停下来
	while (!STempty(&st)) {
		int begin = STtop(&st);
		STpop(&st);
		int end = STtop(&st);
		STpop(&st);
		int key = QuickSort2(a,begin,end);
		//这里分成的是[begin,key - 1] key [key + 1,end]
		if (key + 1 < end) {//小于的是end,每次都不一样
                        //push新的左右区间
			STpush(&st, end);
			STpush(&st, key + 1);
		}
		if ((key - 1) > begin) {
			STpush(&st, key - 1);
			STpush(&st, begin);
		}
	}
	STdes

快排的致命痛点:

代码实现:
点击查看代码

复制代码
//快排的三路划分
void QuickSort3(int* a,int left,int right) {
	if (left >= right)
		return;
	int begin = left;
	int key = a[left];
	int end = right;
	int cur = begin + 1;
	while (cur <= end) {
		if (a[cur] < key) {
			Swap(&a[cur], &a[begin]);
			cur++;
			begin++;
		}
		else if (a[cur] == key) 
			cur++;
		else {
			Swap(&a[cur], &a[end]);
			end--;
		}
	}
	//此时分为三个区间[left,begin-1] [begin,end] [end+1,right]
	QuickSort3(a, left, begin - 1);
	QuickSort3(a, end+1,right);
}

归并排序

核心思想:

无限的二分数组,直到两两有序(只有一个值)然后开始归并

特点:

稳定排序,时间复杂度为O(N*logN),空间复杂度为O(N)

适用场景:

需要稳定排序,数据量较大,或数据存储在外部存储器中

时间复杂度:

由于它基本上是均分数组,是完全二叉树,递归深度为logN层,每层都需要遍历一边,时间为O(N),故时间复杂度为O(N*logN)

代码实现:

递归版:
点击查看代码

复制代码
void _MergeSort(int* a, int* tmp, int left, int right) {
	//当只有一个值了就有序了
	if (left >= right) {
		return;
	}
	int mid = (left + right) / 2;
        //注意:这里选区间有坑,只能这么选
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	_MergeSort(a, tmp, begin1, end1);
	_MergeSort(a, tmp, begin2, end2);
	//当两个数组有序的时候
	int i = begin1;//每次的i是从begin1开始的
	//只要有一个走完了就结束了
	while (begin1 <= end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2]) {
			tmp[i++] = a[begin1++];
		}
		else {
			tmp[i++] = a[begin2++];
		}
	}
	while (begin2 <= end2) {//要取等号,否则单值会漏掉
		tmp[i++] = a[begin2++];
	}
	while (begin1 <= end1) {
		tmp[i++] = a[begin1++];
	}
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));//归并完了要拷贝回去,每次起点不一样
}
void MergeSort(int* a, int size) {
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (tmp == NULL) {
		perror("malloc fail");
		return;
	}//每次malloc完都要检验,如果不开子函数,每次递归的时候都会malloc一个数组,浪费空间
	_MergeSort(a, tmp, 0,size - 1);

	free(tmp);
	tmp = NULL;
}

选区间时的坑:

非递归版:
点击查看代码

复制代码
void MergeSortNonR(int* a, int size) {
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (tmp == NULL) {
		perror("malloc fail");
		return;
	}//每次malloc完都要检验
	int gap = 1;//gap是每组的个数,从一个开始,因为只有一个肯定有序
	while (gap < size) {
		int j = 0;
		//从一 一开始排,然后过渡到两两开始,是由gap来控制的
		for (int i = 0; i < size; i += 2 * gap) {
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//因为此时只有[begin1,end1]已经是有序了,不需要再归并了
			if (begin2 >= size)
				break;
			//处理end2越界的问题,先把end2改成size-1再归并,这样就变成了[begin1,end1]与[begin2,size-1]的归并
			if (end2 >= size)
				end2 = size - 1;
			//只要有一个走完了就结束了
			while (begin1 <= end1 && begin2 <= end2) {
				if (a[begin1] < a[begin2]) {
					tmp[j++] = a[begin1++];
				}
				else {
					tmp[j++] = a[begin2++];
				}
			}
			while (begin2 <= end2) {//要取等号,否则单值会漏掉
				tmp[j++] = a[begin2++];
			}
			while (begin1 <= end1) {
				tmp[j++] = a[begin1++];
			}
			//需要一段一段拷贝,原因:当begin2 >= size的时候此时的end2已经超出size-1了拷贝的数量就不对了
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		printf("\n");
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

代码实现中的坑:

计数排序

核心思想:

新开一个数组,用新数组的下标来表示原数组的数据,遍历一遍原数组,每遇到原数组中的数据,新数组对应的下标的值就++,然后再遍历一边新数组,把新数组的值拷贝回原数组

特点:

非比较排序,时间复杂度为O(N + range),空间复杂度为O(N)

适用场景:

数据类型集中,数值范围远小于数据量(此时时间复杂度为O(N))

时间复杂度:

两次遍历,分别为O(N),O(range),故时间复杂度为O(N+range)

代码实现:

点击查看代码

复制代码
void CountSort(int* a, int size) {
	//先遍历一遍找到最小值和最大值(相对映射)
	int mini = a[0], maxi = a[0];
	for (int i = 0; i < size; i++) {
		if (a[i] < mini)
			mini = a[i];
		if (a[i] > maxi)
			maxi = a[i];
	}
	int range = maxi - mini + 1;
	//calloc自动初始化为0
	//如果用malloc需要用memset手动初始化为0
	int* tmp = (int*)calloc(range,sizeof(int));
	if (tmp == NULL) {
		perror("calloc fail");
		return;
	}
	//放入tmp下标中
	for (int i = 0; i < size; i++) {
		//相对映射,若直接映射会浪费太多空间
		tmp[a[i] - mini]++;
	}
	//拷贝回原数组
	int j = 0;
	for (int i = 0; i < range; i++) {
		while (tmp[i]--) {
			a[j++] = i + mini;
		}
	}
	free(tmp);
	tmp = NULL;
}

相对映射: 先遍历出原数组的最大值与最小值,从而决定新数组的大小,原数组的数据-min值就是新数组对应的下标

归并排序的外排序

选归并的原因:

因为其他的排序都是用数组存储数据,要用下标去访问数据,但在磁盘文件中不方便访问,需要读写文件来访问,速度极慢

核心思路:

将一个大文件的数据放到file1和file2两个小文件中去,并用排序算法排成有序,然后用归并算法将file1和file2合成有序的大文件mfile,删除file1和file2,将mfile改名为file1,然后继续读文件到file2中,继续归并......

代码实现:

点击查看代码

复制代码
//文件的外排序(用归并排序实现)

//设置随机的数
void FileSet() {
	const char* filename = "data.txt";
	FILE* file = fopen(filename, "w");
	if (file == NULL) {
		perror("fopen fail");
		return;
	}
	//写入随机数据
	int size = 1000000;
	for (int i = 0; i < size; i++) {
		fprintf(file, "%d\n", rand() + i);
	}
	fclose(file);
}

//把文件中的数读入新文件中,并排序
//返回实际读到的数据个数
int ReadNDataSortToFile(FILE* file, int n, const char* name) {
	//要是每次都打开文件的话文件指针又会重头开始了,所以要提前打开
	FILE* file1 = fopen(name, "w");
	if (file1 == NULL) {
		perror("fopen fail");
		return 0;
	}
	//把原文件读入临时的数组中
	int* a = (int*)calloc(n, sizeof(int));
	if (a == NULL) {
		perror("calloc fail");
		return 0;
	}
	int index = 0;//因为此时不一定能读满n个
	int x = 0;
	for (int i = 0; i < n; i++) {
		if (fscanf(file, "%d", &x)==EOF)
			break;
		a[index++] = x;//index此时是个数
	}
	if (index == 0) {
		free(a);
		a = NULL;//一定要free掉,不然会内存泄露
		return 0;
	}
	//将数组a排序
	QuickSortNonR(a, 0, index-1);//任意排序均可
	//然后把数组中的值写入file1中
	for (int i = 0; i < index; i++) {
		fprintf(file1, "%d\n", a[i]);
	}
	fclose(file1);
	free(a);
	a = NULL;
	return index;
}

//将两个有序的文件归并在一起
void MergeSortToFile(const char* file1name, const char* file2name,const char* mfilename) {
	FILE* file1 = fopen(file1name, "r");
	if (file1 == NULL) {
		perror("fopen fail");
		return;
	}FILE* file2 = fopen(file2name, "r");
	if (file2 == NULL) {
		perror("fopen fail");
		return;
	}
	FILE* mfile = fopen(mfilename, "w");
	if (mfile == NULL) {
		perror("fopen fail");
		return;
	}
	//将file1和file2归并在一起
	int x1;
	int x2;
	int ret1 = fscanf(file1, "%d", &x1);
	int ret2 = fscanf(file2, "%d", &x2);
	while (ret1 !=EOF && ret2!=EOF) {//这里的ret1和ret2要更新
		if (x1<x2) {
			fprintf(mfile, "%d\n", x1);//要加换行
			ret1 = fscanf(file1, "%d", &x1);
		}
		else {
			fprintf(mfile, "%d\n", x2);
			ret2 = fscanf(file2, "%d", &x2);
		}
	}
	while (ret1 != EOF) {
		fprintf(mfile, "%d\n", x1);
		ret1 = fscanf(file1, "%d", &x1);
	}
	while (ret2!= EOF) {
		fprintf(mfile, "%d\n", x2);
		ret2 = fscanf(file2, "%d", &x2);
	}
	fclose(file1);
	fclose(file2);
	fclose(mfile);
}

void FileTest() {
	srand((unsigned int)time(NULL));
	FileSet();
	const char* filename = "data.txt";
	FILE* file = fopen(filename, "r");
	if (file == NULL) {
		perror("fopen fail");
		return;
	}
	//将这文件的数据写入file1和file2中,分别排序,然后归并在一起
	const char* file1 = "file1.txt";
	const char* file2 = "file2.txt";
	const char* mfile = "mfile.txt";
	int m = 100000;//每次读入的数据个数
	ReadNDataSortToFile(file, m, file1);
	ReadNDataSortToFile(file, m, file2);
	while (1) {
		//将file1和file2归并在一起
		MergeSortToFile(file1, file2, mfile);
		//归并在一起后删除file1和file2
		remove(file1);
		remove(file2);
		//再将mfile名字改成file1,循环操作
		rename(mfile, file1);
		if (ReadNDataSortToFile(file, m, file2) == 0)
			break;
	}
	fclose(file);
}

排序测试

点击查看代码

复制代码
#include"Sort.h"

void SortTest() {
	srand((unsigned int)time(NULL));
	int N = 10000000;
	int* a1 = (int*)malloc(N * sizeof(int));
	int* a2 = (int*)malloc(N * sizeof(int));
	int* a3 = (int*)malloc(N * sizeof(int));
	int* a4 = (int*)malloc(N * sizeof(int));
	int* a5 = (int*)malloc(N * sizeof(int));
	int* a6 = (int*)malloc(N * sizeof(int));
	int* a7 = (int*)malloc(N * sizeof(int));
	int* a8 = (int*)malloc(N * sizeof(int));
	for (int i = 0; i < N; i++) {
		a1[i] = rand() + i+100;
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
		a8[i] = a1[i];
	}

	//冒泡排序
	int begin = clock();
	BubbleSort(a1, N);
	int end = clock();
	printf("BubbleSort:%d\n", end - begin);

	//堆排序
	begin = clock();
	HeapSort(a2,N);
	end = clock();
	printf("HeapSort:%d\n", end - begin);

	//插入排序
	begin = clock();
	InsertSort(a3,N);
	end = clock();
	printf("InsertSort:%d\n", end - begin);

	//希尔排序
	begin = clock();
	ShellSort(a4, N);
	end = clock();
	printf("ShellSort:%d\n", end - begin);

	//选择排序
	begin = clock();
	SelectSort(a5, N);
	end = clock();
	printf("SelectSort:%d\n", end - begin);

	//快排
	begin = clock();
	QuickSort(a6, 0,N-1);
	end = clock();
	printf("QuickSort:%d\n", end - begin);

	//归并排序
	begin = clock();
	MergeSort(a7, N);
	end = clock();
	printf("MergeSort:%d\n", end - begin);

	//计数排序
	begin = clock();
	CountSort(a8, N);
	end = clock();
	printf("CountSort:%d\n", end - begin);

	//验证是否排正确
	int Flag = 1;
	for (int i = 0; i < N; i++) {
		if (a8[i] != a7[i])
			Flag = 0;
	}
	if (Flag == 1) {
		printf("计数排序正确\n");
	}
	else {
		printf("计数排序错误\n");
	}
}

三、总结

数据量小吗?->插入排序

需要稳定排序?->归并排序/计数排序

数据在磁盘还是在内存?->磁盘选择归并,内存选快排

内存资源紧张吗?->原地排序(快排/希尔/堆排)

没有最好的排序算法,只有最适合的排序算法

相关推荐
Remember_9932 小时前
【LeetCode精选算法】滑动窗口专题二
java·开发语言·数据结构·算法·leetcode
Gorgous—l2 小时前
数据结构算法学习:LeetCode热题100-动态规划篇(下)(单词拆分、最长递增子序列、乘积最大子数组、分割等和子集、最长有效括号)
数据结构·学习·算法
你怎么知道我是队长2 小时前
C语言---未定义行为
java·c语言·开发语言
Remember_9933 小时前
【LeetCode精选算法】滑动窗口专题一
java·数据结构·算法·leetcode·哈希算法
Lueeee.3 小时前
v4l2驱动开发
数据结构·驱动开发·b树
漫随流水4 小时前
leetcode回溯算法(77.组合)
数据结构·算法·leetcode·回溯算法
超级大福宝5 小时前
【力扣200. 岛屿数量】的一种错误解法(BFS)
数据结构·c++·算法·leetcode·广度优先
范纹杉想快点毕业5 小时前
C语言实现埃拉托斯特尼筛法
c语言·开发语言
sycmancia6 小时前
C语言学习06——函数的定义
c语言