> 文章列表 > 数据结构__<八大排序> __插入排序 |希尔排序 |选择排序 |堆排序 |快速排序 |归并排序(C语言实现)

数据结构__<八大排序> __插入排序 |希尔排序 |选择排序 |堆排序 |快速排序 |归并排序(C语言实现)

数据结构__<八大排序> __插入排序 |希尔排序 |选择排序 |堆排序 |快速排序 |归并排序(C语言实现)

目录

插入排序 

希尔排序

选择排序

堆排序

冒泡排序

快速排序

hoare法

挖坑法

前后指针法

快排特性总结

三数取中优化

小区间优化

快排非递归

归并排序

归并排序非递归

计数排序

总结

OJ测试


前言目录

插入排序 

//直接插入排序
void InsertSort(int* a, int n)
{// i的取值范围:[0,n-2]for (int i = 0; i < n - 1; i++){//每一趟排序int end = i;int tmp = a[end + 1]; //将tmp视为插入的数字while (end >= 0){if (tmp < a[end]) //若插入的数字小于有序数字的最后一个数{a[end + 1] = a[end]; //将大于tmp的值往后挪--end;}else{break;}}a[end + 1] = tmp;}
}

1、元素集合越接近有序,直接插入排序算法的时间效率越高

2、时间复杂度:O(N^2)

3、空间复杂度:O(1),它是一种稳定的排序算法

4、稳定性:稳定

希尔排序

 

//希尔排序
void ShellSort(int* a, int n)
{//1、gap > 1 预排序//2、gap == 1 直接插入排序int gap = n;while (gap > 1){gap = gap / 3 + 1; //+1能够保证最后一次gap一定是1//控制gap组都进行预排序//这里只是把插入排序的1换成gap即可//但是这里不是排序完一个分组,再去//排序另一个分组,而是整体只过一遍//这样每次对于每组数据只排一部分//整个循环结束之后,所有组的数据排序完成for (int i = 0; i < n - gap; i++){//确保一组中的数据都进行插入排序int end = i;//定义一个变量tmp保存end的后一个数,其下标是end+gapint tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];//如果end下标的数值大于后面的值tmp,也就意味着end下标的值要往后挪end -= gap;}else{break;}}//单趟循环结束或循环中直接break出来均直接赋值a[end + gap] = tmp;}}
}

1、希尔排序是对直接插入排序的优化。

2、当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3、稳定性:不稳定

4、时间复杂度分析:

 希尔排序的时间复杂度不是很好算,我们先简要看下预排序的时间复杂度:

  1. gap很大时,数据跳的很快,里面套的循环可以忽略不记,差不多是O(N)。
  2. gap很小时,数据跳的很慢,很接近有序,差不多也是O(N)

再来看外面套上循环后的时间复杂度:

while循环中的gap = gap / 3 + 1相当于是循环了log{_{3}}^{N}

既然外循环执行log_{3} ^{ N}次,内循环执行N次,那么时间复杂度为O(N*log{_{3}}^{N})。但是上述计算顶多是估算,有人在大量的实验基础上推出其时间复杂度应为:O(N^{1.3})

选择排序

思路:

优化的选择排序,每次可以选择一个最大的和一个最小的,然后把他们放在合适的位置,即最小的放在第一个位置,最大的放在最后一个位置,然后再去选择次小的和次大的,依次这样进行,直到区间只剩一个值或没有

#include<iostream>
#include<string>
#include<stdlib.h> 
using namespace std;//交换
void Swap(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}
void SelectSort(int* a, int n)
{//assert(a);int begin = 0, end = n - 1;while (begin < end){int min = begin, max = begin;for (int i = begin; i <= end; i++){if (a[i] >= a[max])max = i;if (a[i] < a[min])min = i;}//最小的放在Swap(&a[begin], &a[min]);//如果最大的位置在begin位置//说明min是和最大的交换位置//这个时候max的位置就发生了变换//max变到了min的位置//所以要更新max的位置if (begin == max)max = min;Swap(&a[end], &a[max]);++begin;--end;//PrintArray(a, n);}
}
void PrintArray(int a[], int sum)
{for(int i=0;i<sum;i++){cout<<a[i]<<" ";}cout<<endl;
}void TestSelectSort()
{int a[] = { 3, 4, 6, 1, 2, 8, 0, 5, 7 };SelectSort(a, sizeof(a) / sizeof(int));PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{TestSelectSort();return 0;
}

1、直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

2、时间复杂度:O(N^2)

3、空间复杂度:O(1)

4、稳定性:不稳定

易错题: 

使用选择排序对长度为100的数组进行排序,则比较的次数为( )

A.5050

B.4950

C.4851

D.2475


答案:B

解析:

选择排序,每次都要在未排序的所有元素中找到最值,

如果有n个元素,则

第一次比较次数: n - 1

第二次比较次数: n - 2

....

第n - 1次比较次数: 1

所有如果n = 100

则比较次数的总和:99 + 98 + ...... + 1

共4950次。

堆排序

1,建大堆,把根交换到最底,然后在减一个元素继续调整

2,向下调整,继续交换,直到最后一个元素

 

上图的代码: 

void HeapSort(int* a, int n)
{//向下调整建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//大堆升序size_t end = n - 1;while (end > 0){Swap(&a[0], &a[end]);    //为了保持完全二叉树状态AdjustDown(a, end, 0);end--;}
}
#include<iostream>
#include<string>
#include<stdlib.h> 
using namespace std;//交换
void Swap(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}
//向下调整算法
void AdjustDown(int* a, size_t size, size_t root)
{int parent = (int)root;int child = 2 * parent + 1;while (child < size){//1、确保child的下标对应的值最大,即取左右孩子较大那个if (child + 1 < size && a[child + 1] > a[child]) //得确保右孩子存在{child++; //此时右孩子大}//2、如果孩子大于父亲则交换,并继续往下调整if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}
//升序
void HeapSort(int* a, int n)
{//向下调整建堆// 建堆,先从最后两个叶子上的根(索引为(n - 2) / 2开始建堆// 先建最小的堆,直到a[0](最大的堆)// 这就相当于在已经建好的堆上面,新加入一个// 根元素,然后向下调整,让整个完全二叉树// 重新满足堆的性质for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//大堆升序size_t end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}
int main()
{int a[] = { 4,2,7,8,5,1,0,6 };HeapSort(a, sizeof(a) / sizeof(int));for (int i = 0; i < sizeof(a) / sizeof(int); i++){printf("%d ", a[i]);}return 0;
}

1、堆排序使用堆来选数,效率就高了很多。

2、时间复杂度:O(N*logN)

3、空间复杂度:O(1)

4、稳定性:不稳定

冒泡排序

//交换
void Swap(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}
//冒泡排序
void BubbleSort(int* a, int n)
{for (int j = 0; j < n; j++){for (int i = 1; i < n - j; i++){if (a[i - 1] > a[i]){Swap(&a[i], &a[i - 1]);}}}
}

1、冒泡排序是一种非常容易理解的排序

2、稳定性:稳定

3、空间复杂度:O(1)

4、时间复杂度:O(N^2)

最好情况:数组本身是顺序的,外层循环遍历一次就完成O(N)

最坏情况:数组本身是逆序的,内外层遍历O(N^2)

快速排序

hoare法

  1. 选出一个key,一般是第一个数,或者是最后一个数
  2. 定义变量L和R,L从左走,R从右走
  3. R先向前走,找到比key小的位置停下,再让L向后走,找到比key大的值停下
  4. 交换L和R代表的数值
  5. 继续遍历,同样让R先走,L后走,同上规则
  6. 当L和R相遇的时候,把相遇位置的值与key位置的值交换,结束
//hoare
//快排单趟排序
int PartSort(int* a, int left, int right)
{int keyi = left; //选左边作keywhile (left < right){//右边先走,找小while (left < right && a[right] >= a[keyi]) //防止right找不到比keyi小的值直接飙出去,要加上left < right{right--;}//右边找到后,左边再走,找大while (left < right && a[left] <= a[keyi]) //同上,也要加上left < right{left++;}//右边找到小,左边找到大,就交换Swap(&a[left], &a[right]);}//此时left和right相遇,交换与key的值Swap(&a[keyi], &a[left]);return left;
}//快速排序
void QuickSort(int* a, int begin, int end)
{//子区间相等只有一个值或者不存在那么就是递归结束的子问题if (begin >= end){return;}int keyi = PartSort(a, begin, end);//分成左右两段区间递归// [begin, keyi-1] 和 [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

挖坑法

  1.  把最左边的位置用key保存起来,此位置形成坑位
  2. 定义变量L和R分别置于最左和最右
  3. 让R先向前走,找到比key小的位置停下
  4. 找到后,将该值放入坑位,自己形成新的坑位
  5. 再让L向后走,找比key大的位置停下
  6. 找到后,将该值放入坑位,自己形成新的坑位
  7. 再让R走……
  8. 当L和R相遇时,把key的值放到坑位,结束
     
//挖坑法
int PartSort2(int* a, int left, int right)
{//把最左边的值用key保存起来int key = a[left]; //把left位置设为坑位pitint pit = left;while (left < right) //当left小于right时就继续{//右边先走,找小于key的值while (left < right && a[right] >= key){right--; //如若right的值>=key的值就继续}//找到小于key的值时就把此位置赋到坑位,并把自己置为新的坑位a[pit] = a[right];pit = right;//左边走,找大于key的值while (left < right && a[left] <= key){left++;}//找到大于key的值就把此位置赋到坑位,并把自己置为新的坑位a[pit] = a[left];pit = left;}//此时L和R相遇,将key赋到坑位a[pit] = key;return pit;
}//快速排序
void QuickSort(int* a, int begin, int end)
{//子区间相等只有一个值或者不存在那么就是递归结束的子问题if (begin >= end){return;}int keyi = PartSort2(a, begin, end);//分成左右两段区间递归// [begin, keyi-1] 和 [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

前后指针法

  1. 把第一个位置的值设为key保存起来
  2. 定义prev指针指向第一个位置,cur指向prev后一个位置
  3. 若cur指向的数值小于key,prev和cur均后移
  4. 当cur指向的数据大于key时,prev不动,cur继续后移
  5. 当cur的值小于key时,prev后移一位,交换与cur的值,cur再++
  6. 重复上述操作,当cur越界时,交换此时的prev和key的值。结束
     
//前后指针法
int PartSort3(int* a, int left, int right)
{int key = left;//注意不能写成 int key = a[left]int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[key] && a[++prev] != a[cur]) {Swap(&a[prev], &a[cur]);//在cur的值小于key的值的前提下,并且prev后一个值不等于cur的值时交换,避免了交换两个小的(虽然也可以,但是没有意义)}cur++; //如若cur的值大于key,则cur++}Swap(&a[prev], &a[key]); //此时cur越界,直接交换key与prev位置的值return prev;
}//快速排序
void QuickSort(int* a, int begin, int end)
{//子区间相等只有一个值或者不存在那么就是递归结束的子问题if (begin >= end){return;}int keyi = PartSort2(a, begin, end);//分成左右两段区间递归// [begin, keyi-1] 和 [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

快排特性总结

稳定性:不稳定

空间复杂度:O(logN)

时间复杂度:O(N*logN)

快排的时间复杂度分两种情况讨论:

  1. 最好:每次选key都是中位数,通俗讲是左边一半右边一半,具体看是key的左序列长度和右序列长度相同。时间复杂度O(N*logN)
  2. 最坏:每次选出最小的或者最大的作为key。时间复杂度O(N^{2})

易错题:

排序过程中,对尚未确定最终位置的所有元素进行一遍处理称为一“趟”。下列排序中,不可能是快速排序第二趟结果的是()【2019年全国试题10(2分)】

A. 5, 2, 16, 12, 28, 60, 32, 72
B. 2, 16, 5, 28, 12, 60, 32, 72
C. 2, 12, 16, 5, 28, 32, 72, 60
D. 5, 2, 12, 28, 16, 32, 72, 60


答案:D

每经过一趟快排,轴点元素都必然就位。也就是说,一趟下来至少有1个元素在其最终位置。所以考察各个选项,看有几个元素就位即可。

最终排序位置是:2, 5, 12, 16, 28, 32, 60, 72,而选项中正确的位置有:

A. 5, 2, 16, 12, 28, 60, 32, 72
B. 2, 16, 5, 28, 12, 60, 32, 72
C. 2, 12, 16, 5, 28, 32, 72, 60
D. 5, 2, 12, 28, 16, 32, 72, 60

对于D选项,在第一趟排序好,一定能确定一个枢轴元素,要么是12,要么是32,如果是12的话,在第二趟向左递归的时候,一定是2排在5的前面,如果第二趟是先向右递归,那么16肯定排在28的前面,。如果是32的话,跟上面的一个思路,在第二趟排序的时候,如果先向左递归,5一定排在2的后面,如果先向右递归,60也一定排到72的前面,综上,这两种情况d选项都不符合。

三数取中优化

取第一个数,最后一个数,中间那个数,在这三个数中选不是最大也不是最小的那个数作为key。此法针对有序瞬间从最坏变成最好,针对随机数,那么选出来的数也同样不是最大也不是最小,同样进行了优化。

三数取中其实针对hoare法,挖坑法,前后指针法都适用,这里我们就以前后指针法示例:

//快排
//三数曲中优化
int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) / 2; // int mid = left + (right - left) / 2// left  mid  rightif (a[left] < a[mid]){if (a[mid] < a[right]) // left < mid < rightreturn mid;else if (a[left] < a[right]) // left < right <midreturn right;else  // right < left < midreturn left; }else // left > mid{if (a[right] > a[left]) // right > left > midreturn left;else if (a[mid] > a[right])// left > mid > rightreturn mid;else // left > right > midreturn right;}
}//前后指针法
int PartSort3(int* a, int left, int right)
{//三数取中优化int midi = GetMidIndex(a, left, right);Swap(&a[midi], &a[left]);int key = left;//注意不能写成 int key = a[left]int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[key] && a[++prev] != a[cur]) {Swap(&a[prev], &a[cur]); }cur++;  }Swap(&a[prev], &a[key]);  return prev;
}//快速排序
void QuickSort(int* a, int begin, int end)
{//子区间相等只有一个值或者不存在那么就是递归结束的子问题if (begin >= end){return;}int keyi = PartSort3(a, begin, end);//分成左右两段区间递归// [begin, keyi-1] 和 [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

小区间优化

当递归到越小的区间时,递归次数就会越多,针对这一小区间采取插入排序更优,减少了大量的递归次数

//三数取中优化
int GetMidIndex(int* a, int left, int right)
{//……
}//前后指针法
int PartSort3(int* a, int left, int right)
{//三数取中优化int midi = GetMidIndex(a, left, right);Swap(&a[midi], &a[left]);//……
}//小区间优化
void QuickSort2(int* a, int begin, int end)
{//子区间相等只有一个值或者不存在那么就是递归结束的子问题if (begin >= end){return;}//小区间直接插入排序控制有序if (end - begin + 1 <= 10){InsertSort(a + begin, end - begin + 1);}else{int keyi = PartSort3(a, begin, end);// [begin, keyi-1] 和 [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}
}

快排非递归

在快排递归的过程中是要建立栈帧的,仔细看看每次递归时传的参数,有begin和end,其递归过程存储的是排序过程中要控制的区间,那我们用非递归模拟递归的过程中也要按照它这个存储方式进行,这就需要借助

//快排非递归
void QuickSort3(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, left, right); // keyi已经到了正确位置// [left, kryi-1]  [keyi+1, right]if (left < keyi - 1)//如若左区间不只一个数就入栈{StackPush(&st, left);StackPush(&st, keyi - 1);}if (keyi + 1 < right)//若右区间不只一个就入栈{StackPush(&st, keyi + 1);StackPush(&st, right);}}StackDestory(&st);
}

归并排序

如图所示,先分为最小单元,利用数组tmp排序,然后回溯重复操作 

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); //递归右半//归并[begin, mid] [mid+1, end]//printf("归并[%d,%d][%d,%d]\\n", begin, mid, mid + 1, end);int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int index = begin;while (begin1 <= end1 && begin2 <= end2){//将较小的值放到tmp数组里头if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}//如若begin2先走完,把begin1后面的元素拷贝到新数组while (begin1 <= end1){tmp[index++] = a[begin1++];}//如若begin1先走完,把begin2后面的元素拷贝到新数组while (begin2 <= end2){tmp[index++] = a[begin2++];}//归并结束后,把tmp数组拷贝到原数组memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}//归并排序
void MergeSort(int* a, int n)
{//malloc一块新数组int* tmp = (int*)malloc(sizeof(int) * n);assert(tmp);_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

 1、归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2、时间复杂度:O(N*logN)

3、空间复杂度:O(N)

4、稳定性:稳定 

归并排序非递归

  • 思想:

归并的非递归不需要借助栈,直接使用循环即可。递归版中我们是对数组进行划分成最小单位,这里非递归我们直接把它看成最小单位进行归并。我们可以通过控制间距gap来完成

//归并非递归
void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);assert(tmp);int gap = 1;while (gap < n){//分组归并,间距为gap是一组,两两归并for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//end1越界,修正即可if (end1 >= n){end1 = n - 1;}//begin2越界,第二个区间不存在if (begin2 >= n){begin2 = n;end2 = n - 1;}//begin2 ok,end2越界,修正下end2即可if (begin2 < n && end2 >= n){end2 = n - 1;}printf("归并[%d,%d][%d,%d]\\n", begin1, end1, begin2, end2);int index = i;while (begin1 <= end1 && begin2 <= end2){//将较小的值放到tmp数组里头if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}//如若begin2先走完,把begin1后面的元素拷贝到新数组while (begin1 <= end1){tmp[index++] = a[begin1++];}//如若begin1先走完,把begin2后面的元素拷贝到新数组while (begin2 <= end2){tmp[index++] = a[begin2++];}}memcpy(a, tmp, n * sizeof(int));gap *= 2;}free(tmp);
}

优化+完整版 


/*
非递归排序与递归排序相反,将一个元素与相邻元素构成有序数组,
再与旁边数组构成有序数组,直至整个数组有序。
要有mid指针传入,因为不足一组数据时,重新计算mid划分会有问题
需要指定mid的位置
*/
void merge(int* a, int left, int mid, int right, int* tmp)
{// [left, mid]// [mid+1, right]int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}memcpy(a+left, tmp+left, sizeof(int)*(right - left+1));
}/*
k用来表示每次k个元素归并
*/
void mergePass(int *arr, int k, int n, int *temp)
{int i = 0;//从前往后,将2个长度为k的子序列合并为1个while(i < n - 2*k + 1){merge(arr, i, i + k - 1, i + 2*k - 1, temp);i += 2*k;}//合并区间[i, n - 1]有序的左半部分[i, i + k - 1]以及不及一个步长的右半部分[i + k, n - 1]if(i < n - k ){merge(arr, i, i + k - 1,n-1, temp);}}// 归并排序非递归版
void MergeSortNonR(int *arr,int length)
{int k = 1;int *temp = (int *)malloc(sizeof(int) * length);while(k < length){mergePass(arr, k, length, temp);k *= 2;}free(temp);
}void TestMergeSort()
{int a[] = { 3, 4, 6, 1, 2, 8, 0, 5, 7 };MergeSort(a, sizeof(a) / sizeof(int));PrintArray(a, sizeof(a) / sizeof(int));
}

 1、归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2、时间复杂度:O(N*logN)

3、空间复杂度:O(N)

4、稳定性:稳定 

计数排序

  • 绝对映射:原数组是几,映射到新数组下标位置++
  • 相对映射:此时新数组下标的范围是从0到原数组最小的值,而映射到下标的位置为原数组val的值 - 原数组最小min的值
//计数排序
void CountSort(int* a, int n)
{int min = a[0], max = a[0];//先求出原数组的最大和最小值for (int i = 1; i < n; i++){if (a[i] < min)min = a[i];if (a[i] > max)max = a[i];}//求出新数组的范围int range = max - min + 1;//开辟新数组int* countA = (int*)malloc(sizeof(int) * range);assert(countA);//把新开辟数组初始化为0memset(countA, 0, sizeof(int) * range);//计数for (int i = 0; i < n; i++){countA[a[i] - min]++; //统计相同元素出现次数(相对映射)}//排序int j = 0;for (int i = 0; i < range; i++){while (countA[i]--){a[j++] = i + min; //赋值时,记得加回原先的min}}free(countA);
}

完整版 

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* countArray = (int*)malloc(range*sizeof(int));memset(countArray, 0, sizeof(int)*range);//存放在相对位置,可以节省空间for (int i = 0; i < n; ++i){countArray[a[i] - min]++;}//可能存在重复的数据,有几个存几个int index = 0;for (int i = 0; i < range; ++i){	while (countArray[i]--){a[index++] = i+min;}}
}void TestCountSort()
{int a[] = { 3, 4, 6, 2, 8, 5, 2, 2, 9, 9, 1000000, 99999};CountSort(a, sizeof(a) / sizeof(int));PrintArray(a, sizeof(a) / sizeof(int));
}void TestSortOP()
{const int n = 1000000;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);int* a8 = (int*)malloc(sizeof(int)*n);srand(time(0));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];a8[i] = a1[i];}a8[n] = 100000000;size_t begin1 = clock();//InsertSort(a1, n);size_t end1 = clock();printf("%u\\n", end1 - begin1);size_t begin2 = clock();ShellSort(a2, n);size_t end2 = clock();printf("%u\\n", end2 - begin2);size_t begin3 = clock();//SelectSort(a3, n);size_t end3 = clock();printf("%u\\n", end3 - begin3);size_t begin4 = clock();HeapSort(a4, n);size_t end4 = clock();printf("%u\\n", end4 - begin4);size_t begin5 = clock();//BubbleSort(a5, n);size_t end5 = clock();printf("%u\\n", end5 - begin5);size_t begin6 = clock();QuickSort(a6, 0, n-1);size_t end6 = clock();printf("%u\\n", end6 - begin6);size_t begin7 = clock();MergeSort(a7, n);size_t end7 = clock();printf("%u\\n", end7 - begin7);size_t begin8 = clock();CountSort(a8, n);size_t end8 = clock();printf("%u\\n", end8 - begin8);
}
  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(N + range)
  3. 空间复杂度:O(range)
  4. 稳定性:稳定

总结

  • 内排序:数据量较少,在内存中进行排序
  • 外排序:数据量很大,在磁盘上进行排序
  • 综上1G = 1024*1024*1024Byte,而10亿个整数40亿Byte,所以10亿个整数占4G,即1e9以下可内排序,以上必须外排序

OJ测试

912. 排序数组 - 力扣(LeetCode)

 

/*
此题对于时间效率要求较高,像插入排序,选择排序,冒泡排序都是O(n^2)的时间复杂度,所以这三种排序都跑不过。
*/
int* sortArray(int* nums, int numsSize, int* returnSize){//插入排序, 此题如果用插入排序,时间复杂度过高,会导致TLEInsertSort(nums, numsSize);//希尔ShellSort(nums, numsSize);//选择,会超出时间限制SelectSort(nums, numsSize);//冒泡排序, 也会超出时间限制BubbleSort(nums, numsSize);//快排QuickSort(nums, 0, numsSize - 1);//归并MergeSort(nums, numsSize);//计数CountSort(nums, numsSize);*returnSize = numsSize;return nums;
}