常见的排序算法
前言: 文章主要介绍以下几大类排序算法
说明: 文章中的代码示例均为升序写法
1.插入排序
1.1直接插入排序
基本思想
当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移
代码
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
//单趟插入排序
int end = i;
//把要插入的数据保存起来
//理由:将后一个数据往前插入,所以需要把后一个数据保存起来
int tmp = a[end + 1]; //要插入的数据
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp; //插入的数据永远放到结束位置的后一个
}
}
分析
1.时间复杂度: O(N^2) (在逆序情况下最坏,时间复杂度成等差数列为O(N^2))
有序或接近有序时最好, 顺序有序时间复杂度为O(N)
2.空间复杂度: O(1)
3.稳定性: 稳定
(稳定性: 简单来说排序一组数, 如果相同的数可以保证它们的相对顺序不变,那就是稳定的)
1.2希尔排序
基本思想
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
总结起来就是: 预排序(分组排序)+插入排序的过程
过渡过程
在写希尔排序代码前的过渡过程: 分为3个过渡部分
希尔排序简单来说就是高级的直接插入排序
画图讲解:
1.
代码
void ShellSort1(int* a, int n)
{
int gap = 3;
for (int i = 0; i < n - 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=1的一一比较换成了gap=3的组内比较
实现的排序效果:(以数组 9 8 7 6 5 4 3 2 1 为例)
2.
代码
void ShellSort2(int* a, int n)
{
int gap = 3;
for (int j = 0; j < gap; j++)
{
for (int i = j; i < n - 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;
}
}
}
从第1个过程写第2个过程,过程2在过程1上就是加了一个循环,实现了一个组距为gap的3组排序
实现的排序效果:(以数组 9 8 7 6 5 4 3 2 1 为例)
3.
代码
void ShellSort3(int* a, int n)
{
int gap = 3;
for (int i = 0; i < n - gap; i++)
{
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;
}
}
}
3是2的升级, 3在2的基础上去掉了一层循环却实现了相同的功能,其实现方法如上图所示,不用外加循环一组一组进行gap组排序而是直接实现gap组并排
实现的排序效果:(以数组 9 8 7 6 5 4 3 2 1 为例)
总结: 以上希尔排序的过渡过程实现了预排序,使要排序的数组接近有序,要达到真正的有序要在数组接近有序的条件下进行直接插入排序
实现真正的希尔排序两大过程: 预排序(分组排序)+插入排序
1.预排序: 在过渡过程中已经实现, 现在唯一要解决的是要确定gap的值,为何上面演示例子中gap=3
2.插入排序: 在预排序的情况完成下,只需使最后一次排序为直接插入排序,即最后一次排序使gap=1
主要问题: 确定gap的值,并且要保证gap是可变的
gap取值问题:
排序中gap取值不同排序出来的效果:
由此得出以下结论:
gap越大,大的数可以更快到后面,小的数可以更快到前面。越不接近有序。
gap越小,数据跳动越慢。越接近有序。
gap最终取值:
上面两种取法任意一种即可
在外面添加一层循环, 保证每次gap的值是可变的
代码
void ShellSort(int* a, int n)
{
//gap>1 预排序
//gap=1 直接插入排序
int gap = n;
while (gap > 1)
{
gap = gap/3 + 1;
for (int i = 0; i < n - gap; i++)
{
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;
}
}
}
}
分析
-
时间复杂度: O(N^1.3)(不固定)
希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定
-
空间复杂度: O(1)
-
稳定性: 不稳定
(预排序可能会使相同的数据分到不同的组中)
2.选择排序
2.1直接选择排序
基本思想
在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素。
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。
在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。
代码
原版:
只找最大值或最小值来排序
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void SelectSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int mini = i;
for (int j = i+1; j < n; j++)
{
if (a[j] < a[mini])
{
mini=j;
}
}
Swap(&a[mini], &a[i]);
}
}
优化版:
找最大值和最小值来排序
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
int mini = begin;
int maxi = begin;
for (int i = begin+1; i <=end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[mini], &a[begin]);
//更新成原来为i的下标
if (maxi == begin)
maxi = mini;
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
分析
- 时间复杂度: O(N^2)
- 空间复杂度: O(1)
- 稳定性: 不稳定
跟直接插入排序比较, 谁更好 — 直接插入排序
直接插入排序适应性很强, 对于有序或局部有序, 都能效率提升font>
选择排序任何情况下都是O(N^2), 包括有序或接近有序/font>
2.2堆排序
见文章链接: 链接
3.交换排序
3.1冒泡排序
基本思想
代码
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int exchange = 0;
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
exchange = 1;
Swap(&a[j], &a[j+1]);
}
}
//如果一趟冒泡排序中并没有发生交换,说明原数组已经有序,不需要再处理
if (exchange == 0)
{
break;
}
}
}
分析
-
时间复杂度:O(N^2)
-
空间复杂度:O(1)
-
稳定性:稳定
3.2 快速排序
基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序有递归和非递归两种写法
递归
过渡过程
1. hoare版本
[6,1,2,7,9,3,4,5,8,10]通过一趟快速排序达到了以下效果:
基于hoare提出的方法,写出最初的代码:
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int left = begin, right = end;
int keyi = left;
//一趟快速排序
while (left < right)
{
//右边先走,找小
while (left<right && a[right] >= a[keyi])
{
right--;
}
//左边再走,找大
while (left<right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
//[begin, keyi-1] keyi [keyi+1, end]
//递归左区间和右区间
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
此代码2个小细节问题:
while (left < right)
while (left<right && a[right] >= a[keyi])
1. 最外层的while循环已经判断了 left<right, 内层循还是要判断left<right
一旦满足条件从外层循环进入内层循环,在外层只判断了一次left<right,而内层也是循环会进入多次,每一次进入内层循环必须要满足left<right的条件
2. a[right]>=a[keyi]和a[left]<=a[keyi]中必须有‘=’,否则会出现错误
(1)a[keyi]右边所有值都大于左边时, 没有’=‘会出现越界的问题
(2)左右两边有跟a[keyi]相等的值时, 没有’='会出现死循环的问题
因为还有两种方法,所以快速排序代码分成两部分: 框架+单趟排序的过程
hoare代码
int PartSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
int keyi = left;
//一趟快速排序
while (left < right)
{
//右边先走,找小
while (left < right && a[right] >= a[keyi])
{
right--;
}
//左边再走,找大
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
框架
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int keyi = PartSort1(a, begin, end);
//[begin, keyi-1] keyi [keyi+1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
2. 挖坑法
挖坑代码
int PartSort2(int* a, int begin, int end)
{
int left = begin;
int key = a[left];
int right = end;
int hole = left;
//一趟快速排序
while (left < right)
{
//右边找小, 填到左边的坑里
while (left < right && a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
//左边找大, 填到右边的坑里
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
//让key填到自己的坑位上
a[hole] = key;
return key;
}
挖坑和hoare版本有些相似,二者都是左右指针控制来进行一趟快速排序,不同的是挖坑法不像hoare法交换元素,挖坑法设置了一个坑位,将第一个数据存储到临时变量key中, 形成一个坑位,通过多次填坑,使key这个元素确定自己的最终位置
3.前后指针版本
不同于上面两种写法, 此法代码简单好写但是难以理解
总结下来写法:
前后指针代码
版本1:
int PartSort3(int* a, int begin, int end)
{
int key = begin;
int prev = begin;
int cur = begin+1;
while (cur <=end)
{
if (a[cur]<a[key])
{
//找到比key小的值时, 跟++prev交换, 小的往前翻, 大的往后翻
Swap(&a[++prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
return key;
}
版本2:
int PartSort3(int* a, int begin, int end)
{
int key = begin;
int prev = begin;
int cur = begin + 1;
while (cur <= end)
{
//优化了一下,如果prev和cur指向同一个元素不用交换
if (a[cur] < a[key] && ++prev != cur)
{
//找到比key小的值时, 跟++prev交换, 小的往前翻, 大的往后翻
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
return key;
}
两种优化
1.三数取中
快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,所以快速排序的时间复杂度理想状况下O(N*logN)
但是如果一组数据为顺序有序或逆序有序来排序,那么每次都选取第一个元素作为key,代码效率会很低
三数取中的出现是为了解决这类问题
代码
int GetMidIndex(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[end] > a[mid])
{
return mid;
}
else if (a[end] < a[begin])
{
return begin;
}
else
{
return end;
}
}
else //a[begin] > a[mid]
{
if (a[end] < a[mid])
{
return mid;
}
else if (a[end] > a[begin])
{
return begin;
}
else
{
return end;
}
}
}
三数取中放在快速排序单趟的最前面来优化,代码写法见下文
2.小区间优化
解决快速排序最后几次递归调用时递归次数太多的问题, 最后几次递归调用用直接插入排序来处理
小区间优化放在快速排序的框架来优化,代码写法见下文
非递归
栈详见链接: 链接
思路
非递归的思路很像递归, 首先了解递归的关键是区间,非递归也是。
依次把我们需要单趟排的区间入栈,依次取栈里面的区间出来单趟排,再把需要处理的子区间入栈,以此循环,直到栈为空的时候即处理完毕。
代码
void QuickSortNonR(int* a, int begin, int end)
{
ST st;
StackInit(&st);
//先把第一段区间进去,先入左再入右
StackPush(&st, begin);
StackPush(&st, end);
while (!StackEmpty(&st))
{
//单个区间,先出右再出左
int right = StackTop(&st);
StackPop(&st);
int left = StackTop(&st);
StackPop(&st);
//单趟排序
int keyi = PartSort3(a, begin, end);
//由keyi划分出来的区间,先排序左区间,再排序右区间 --> 先入右有区间再入左区间
//[left, keyi-1] keyi [keyi+1, right]
//区间剩一个数不用进栈或区间不存在
if (keyi + 1 < right)
{
StackPush(&st, keyi + 1); //单个区间先入左再入右,先出左再出右
StackPush(&st, right);
}
if (left < keyi - 1)
{
StackPush(&st, left);
StackPush(&st, keyi - 1);
}
}
StackDestroy(&st);
}
优点
当递归深度过大会存在栈溢出的问题。
非递归不存在栈溢出的风险,数据结构的栈不太会崩因为是动态开辟的,动态开辟的栈在堆上,堆空间远大于栈空间
代码(递归)
三数取中
int GetMidIndex(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[end] > a[mid])
{
return mid;
}
else if (a[end] < a[begin])
{
return begin;
}
else
{
return end;
}
}
else //a[begin] > a[mid]
{
if (a[end] < a[mid])
{
return mid;
}
else if (a[end] > a[begin])
{
return begin;
}
else
{
return end;
}
}
}
框架
void QuickSort(int* a, int begin, int end)
{
//该区间不存在
if (begin >= end)
return;
if ((end - begin + 1) < 15) //这里的'15'也可以调整成其他数(比如说十几)
{
InsertSort(a + begin, end - begin + 1);
}
else
{
int keyi = PartSort2(a, begin, end);
//[begin,keyi-1] keyi [keyi+1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
}
1. hoare版本
int PartSort1(int* a, int begin, int end)
{
//三数取中
int mid = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[mid]);
int left = begin;
int right = end;
int keyi = left;
//一趟快速排序
while (left < right)
{
//右边先走,找小
while (left < right && a[right] >= a[keyi])
{
right--;
}
//左边再走,找大
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
2. 挖坑法
int PartSort2(int* a, int begin, int end)
{
int mid = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[mid]);
int left = begin;
int key = a[left];
int right = end;
int hole = left;
//一趟快速排序
while (left < right)
{
//右边找小, 填到左边的坑里
while (left < right && a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
//左边找大, 填到右边的坑里
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
//让key填到自己的坑位上
a[hole] = key;
return key;
}
3. 前后指针版本
第一版
int PartSort3(int* a, int begin, int end)
{
int mid = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[mid]);
int key = begin;
int prev = begin;
int cur = begin+1;
while (cur <=end)
{
if (a[cur]<a[key])
{
//找到比key小的值时, 跟++prev交换, 小的往前翻, 大的往后翻
Swap(&a[++prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
return key;
}
第二版
int PartSort3(int* a, int begin, int end)
{
int mid = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[mid]);
int key = begin;
int prev = begin;
int cur = begin+1;
while (cur <=end)
{
if (a[cur]<a[key])
{
//找到比key小的值时, 跟++prev交换, 小的往前翻, 大的往后翻
Swap(&a[++prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
return key;
}
分析(递归过程)
- 时间复杂度: O(N*logN)
- 空间复杂度: O(1ogN) (递归的深度是logN)
- 稳定性: 稳定
4.归并排序
基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
递归
单趟的思路有些像leetcode这题: 88. 合并两个有序数组 - 力扣(LeetCode)
总结: 开辟一个新的数组,单趟排序就是取小的数尾插到新的数组中后拷贝到原数组
代码
void _MergeSort(int*a, int begin, int end, int*tmp)
{
if (begin >= end)
{
return;
}
//划分子区间
int mid = (begin + end) / 2;
//[begin, mid] [mid+1, end] 递归让子区间有序
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
int begin1 = begin, end1 = mid;
int begin2 = mid+1, end2 = end;
int i = begin;
//取小的尾插
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
//数组2先结束,数组1直接尾插
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
//数组1先结束,数组2直接尾插
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
//拷贝到原数组
memcpy(a+begin, tmp+begin, sizeof(int)* (end - begin + 1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
非递归
思路
用循环来写,设置rangN为每趟归并中每组的数据个数,最开始rangN=1,每组归并中数的个数为1,两组两组归并,归并完后要拷贝回去,可以整体归并完一趟再整体,拷贝回去,也可以归并完一次拷贝回去,每趟归并完后rangN*=2,就完成了一趟归并
坑
一边归并一边拷贝
void MergeSortNonR1(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
//rangN归并每组数据,从1开始,因为1个认为是有序的,可以直接归并
int rangN = 1;
while (rangN < n)
{
//一趟归并
for (int i = 0; i < n; i += rangN * 2)
{
//一次归并
int begin1 = i, end1 = i + rangN - 1;
int begin2 = i + rangN, end2 = i + 2 * rangN - 1;
int j = i;
//end1, begin2, end2越界
if (end1 >= n)
{
break;
}
//begin2, end2越界
else if (begin2 >= n)
{
break;
}
//end2越界
else if (end2 >= n)
{
end2 = n - 1;
}
//取小的尾插
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
//归并一部分,拷贝一部分,将一次归并的结果拷贝回去
memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));
}
rangN *= 2;
}
free(tmp);
tmp = NULL;
}
整体归并完再拷贝
void MergeSortNonR2(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
//rangN归并每组数据,从1开始,因为1个认为是有序的,可以直接归并
int rangN = 1;
while (rangN < n)
{
//一趟归并
for (int i = 0; i < n; i += rangN * 2)
{
//一次归并
int begin1 = i, end1 = i + rangN - 1;
int begin2 = i + rangN, end2 = i + 2 * rangN - 1;
int j = i;
//修正区间 -> 整体拷贝数据
//end1, begin2, end2越界
if (end1 >= n)
{
//修正成不存在的区间
end1 = n-1;
begin2 = n;
end2 = n - 1;
}
//begin2, end2越界
else if(begin2>=n)
{
begin2 = n;
end2 = n - 1;
}
//end2越界
else if (end2 >= n)
{
end2 = n - 1;
}
//取小的尾插
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
}
//整体归并完再拷贝
memcpy(a, tmp, sizeof(int) * n);
rangN *= 2;
}
free(tmp);
tmp = NULL;
}
分析
- 时间复杂度: O(N*logN)
-
空间复杂度: O(N)
-
稳定性: 稳定
5.计数排序
基本思想
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
- 统计相同元素出现次数
- 根据统计的结果将序列回收到原来的序列中
代码
void CountSort(int* a, int n)
{
int max = a[0], min = a[0];
//找最大数和最小数,以确定开辟数组的大小
for (int i = 0; i < n; ++i)
{
if (a[i] > max)
max = a[i];
if (a[i] < min)
min = a[i];
}
int range = max - min + 1;
int* countA = (int*)calloc(range, sizeof(int));
if (countA == NULL)
{
perror("calloc fail");
exit(-1);
}
//1. 统计次数
for (int i = 0; i < n; ++i)
{
countA[a[i] - min]++; //存放的是数据出现的次数
}
//2. 排序
int k = 0;
for (int j = 0;j<range; j++)
{
while (countA[j]--)
{
a[k++] = j + min;
}
}
free(countA);
countA = NULL;
}
分析
- 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
- 时间复杂度:O(MAX(N,范围))
- 空间复杂度:O(范围)
- 稳定性:稳定
排序总结
排序性能评估
// 测试排序的性能对比
void Test()
{
srand(time(0));
const int N = 10000;
int* a1 = (int*)malloc(sizeof(int)*N);
int* a2 = (int*)malloc(sizeof(int)*N);
int* a3 = (int*)malloc(sizeof(int)*N);
int* a4 = (int*)malloc(sizeof(int)*N);
int* a5 = (int*)malloc(sizeof(int)*N);
int* a6 = (int*)malloc(sizeof(int)*N);
int* a7 = (int*)malloc(sizeof(int)*N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
a7[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a4, 0, N-1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
八大排序时间/空间复杂度一览
稳定性的意义:
比如我们做了一个考试系统,考生当中先交卷的,成绩在数组的前面,后交卷的,成绩在数组后面。当我们对前几名进行排名的时候,就可能会遇见几个分值相同的考生,这时候为了公平性考试可以把用时较短者排在前面。