C语言八大排序详解

目录

  • 排序的概念及其应用
  • 常见的八大排序算法
    • 直接插入排序
      • 直接插入排序代码实现
      • 直接插入排序算法时间复杂度及稳定性分析
    • 希尔排序
      • 希尔排序代码实现
      • 希尔排序算法时间复杂度及稳定性分析
    • 选择排序
      • 选择排序代码实现
      • 选择排序算法时间复杂度及稳定性分析
    • 堆排序
      • 堆排序代码实现
      • 堆排序算法时间复杂度及稳定性分析
    • 冒泡排序
      • 冒泡排序代码实现
      • 冒泡排序算法时间复杂度及稳定性分析
    • 快速排序
      • 快速排序三种方法代码实现
      • 快速排序算法时间复杂度及稳定性分析
    • 归并排序
      • 归并排序代码实现
      • 归并排序算法时间复杂度及稳定性分析
    • 计数排序
      • 计数排序代码实现
      • 计数排序算法时间复杂度分析
  • 八大排序“华山论剑”,谁能称帝
  • 总结

排序的概念及其应用

排序的概念及其运用

1、排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

2、排序的运用
(1)、淘宝网上对商品的排序,可以根据品牌、销量、性能、价格等进行排序,我们可以根据不同的排序方式,筛选出自己想要的结果。


(2)2021年国内大学排名

3、常见的排序算法

常见的八大排序算法

直接插入排序

1、基本思想
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

当插入第i(i>=1)个元素时,前面的arr[0],arra1],…,arr[i-1]已经排好序,此时用array[i]的排序码与arr[i-1],arr[i-2],…的排序码顺序进行比较,找到插入位置即将arr[i]插入,原来位置上的元素顺序后移

直接插入排序代码实现

#pragma once
#include<time.h>
#include<stdlib.h>
// 插入排序  时间复杂度O(N*N),数组逆序的情况下
void InsertSort(int* a, int n)
{//多趟排序int i = 0;for (i = 0; i < n - 1; i++)        //{ 5,2,4,6,1,3 }{//单层排序int end = i;                  //end的区间在[0,n-1),n-1的位置放tmpint tmp = a[end + 1];while (end >= 0){if (tmp < a[end])          //将‘<’改成‘>'就是降序排列{a[end + 1] = a[end];  //记得要把比tmp大的数向后移一位end--;}else{break;}}a[end + 1] = tmp;}
}//打印函数
void Print(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}
int main()
{int a[] = { 5,2,4,6,1,3 };int n = sizeof(a) / sizeof(a[0]);InsertSort(a, n);Print(a, n);return 0;
}

直接插入排序算法时间复杂度及稳定性分析

时间复杂度分析:

1、最坏情况,就是逆序情况下排序,例如要排升序,而所要排序的数组是降序,那么时间复杂度就是O(N^2).



如果要排升序,而数组本身就是升序,那么时间复杂度就最优,O(N).
而时间复杂度是以最坏标准来衡量,所以直接插入排序的时间复杂度是O(N^2).

插入排序是稳定排序算法吗?

所谓稳定,指的是当数组中出现相同数值元素的时候,排序是否会造成相同数值元素相对位置的改变。可以看出在插入排序算法中,对于相同数值的元素可以选择插入到已排序区间等值元素的后边,所以相同数值元素的相对位置不会发生改变,因此插入排序算法是稳定的排序算法。

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

希尔排序

基本排序算法中的插入排序虽然逻辑较为简单,但当排序规模较大时,经常需要将元素一位一位地从一端移动到另一端,效率非常低。于是Donald Shell设计了一种基于插入排序的改进版排序算法,故被命名为 Shell Sort 希尔排序。

希尔排序是根据插入排序推想出来的,因为插入排序在最好的情况下,时间复杂度可以达到O(N),所以希尔想到,能不能找到一种方法,使所要排的序列接近有序,这样再排序就可以大大降低时间复杂度。

希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达gap=1时,就是上述的插入排序。

gap越大,大的和小的数可以更快的挪到对应的方向去
gap越大,越不接近有序

gap越小,大的和小的数可以更慢的挪到对应的方向去
gap越小,就越接近有序

gap==1;
就是直接插入排序。

希尔排序代码实现

#pragma once
#include<time.h>
#include<stdlib.h>
void ShellSort(int* a, int n)
{// gap > 1的时候,预排序// gap == 1的时候,直接插入排序  O(N)int gap = n;while (gap > 1) // n/3/3/3.../3 == 1  -》 3^x = n  x就是这个while循环跑的次数{gap = (gap / 2 );    //一般写成gap = (gap / 2+1 );+1是为了保证,最后一次gap为1// 最坏的情况:逆序,gap很大的时-》O(N)// ...// //                   gap很小时本来应该是O(N*N),但是经过前面的预排序,数组已经很接近有序的,所这里还是O(N)for (int i = 0; i < n - gap; ++i){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else {break;}}a[end + gap] = tmp;}printf("gap:%d->", gap);Print(a, n);}
}
int main()
{int a[] = { 9,1,2,5,7,4,8,6,3,5 };int n = sizeof(a) / sizeof(a[0]);ShellSort(a, n);Print(a, n);return 0;
}

希尔排序算法时间复杂度及稳定性分析

时间复杂度分析:

希尔排序的时间复杂度比较难以计算,因为所选的gap不一样,时间复杂度也会有所区别,网上给出希尔排序的平均时间复杂度为O(N^1.3),在这里,根据上述所写代码,计算希尔算法的时间复杂度。

稳定性分析

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
  4. 稳定性:不稳定

选择排序

1、基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

2、 直接选择排序:
在元素集合arr[i]–》arr[n-1]中选择关键码最大(小)的数据元素若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的arr[i]–arr[n-2](arr[i+1]–arr[n-1])集合中,重复上述步骤,直到集合剩余1个元素


上图排序是依次选择最大数放在最右边,成为升序,也可以依次选择最小数放左边,排成升序。当然排成降序也可以,只要稍微改动一下就行。

选择排序代码实现

这里代码实现与上图展示有一点区别,稍微改动一下:

同时选出最大数与最小数,最大数放在右边,最小数放在左边,这样排序的效率会增加一倍。

注意:

当将找到最大数与最小数时,开始交换,最大数与最右边数交换,最小数与最左边数交换。可能会出现一种情况,就是最大数与最右边数交换时,最右边数恰好时最小数,这样就把最小数换走了,原来标记最小数的位置也不是最小数了,最小数就被换成在最大数的位置了,这样排序就会产生错误。

例:

#pragma once
#include<time.h>
#include<stdlib.h>//打印
void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
// 选择排序
void SelectSort(int* a, int n)  //{ 0, 1, 5, 3, 6, 7, 4, 2, 8, 9 }
{int left = 0;int right = n - 1;while (left < right){//选出最大的放在右边,最小的放在左边int max = left;int min = left;for (int i = left; i <= right; i++){if (a[i] > a[max]){max = i;}else if (a[i] < a[min]){min = i;}}Swap(&a[max], &a[right]);//有可能最小的位置在最右边,被max换走了,换到最大的位置去了if (right == min){min = max;      //把最大的位置给最小的位置}Swap(&a[min], &a[left]);left++;right--;//PrintArray(a, n);}
}
int main()
{int arr[] = { 5,8,6,3,9,2,1,7 };int n = sizeof(arr) / sizeof(arr[0]);SelectSort(arr,  n);PrintArray(arr, n);return 0;
}

选择排序算法时间复杂度及稳定性分析

时间复杂度分析:

因为选择排序,需要遍历选数,假设每次只选一个数,时间复杂度为:
N+(N-1)+(N-2)+…+2+1=((N+1)*N)/2,即时间复杂度为 O(N^2),
哪怕,每次选两个数,最大值和最小值排序进行排序,效率也只提升一倍,时间复杂度还是O(N^2)

稳定性分析:

举例说明:

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

堆排序

基本思想

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
堆是完全二叉树

1、堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆(大堆或小堆),才能调整。

向下调整

2、堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆

在此对数组 a[]={20,17,4,16,5,3}排升序。

排序要先建堆,排升序建大堆,排降序,建小堆,在排序的过程种会破环堆结构,需要进行向下调整,重新恢复堆结构。

堆排序代码实现

#pragma once
#include <stdio.h>
#include<stdlib.h>
void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 向下调整算法
void AdjustDwon(int* a, int n, int root)
{int parent = root;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] > a[child]){child++;      //选出左右孩子中大的那一个}if (a[child] > a[parent])   //孩子大于父亲{//交换孩子与父亲位置Swap(&a[parent], &a[child]);//将孩子的位置给父亲,父亲再向下调整parent = child;child = parent * 2 + 1;}else{break;}}
}
//O(N*log(N))
void HeapSort(int* a, int n)
{//先建堆//从最后一个非叶子位置开始调整for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDwon(a, n, i);}//建好大堆,排升序,将堆第一个位置与最后一个位置交换int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);--end;}
}int main()
{int a[] = { 20,17,4,16,5,3 };int n = sizeof(a) / sizeof(a[0]);HeapSort(a, n);PrintArray(a, n);return 0;
}

堆排序算法时间复杂度及稳定性分析

时间复杂度分析:
可能有些难以理解堆排序的时间复杂度,记住就行O(N*logN)

稳定性分析:

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

冒泡排序

基本思想:

就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,冒泡排
序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
例:

冒泡排序代码实现

#pragma once
#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#include<stdio.h>void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void BubbleSort(int* a, int n)
{int i = 0;for (i = 0; i < n - 1; i++)//总趟数{int exchange = 0;int j = 0;for (j = 0; j < n - 1 - i; j++){if (a[j] > a[j + 1]){Swap(&a[j], &a[j + 1]);exchange = 1;}}//if (exchange == 0)//{//  break;       //如果本来就是有序的,直接退出循环//}printf("第%d趟排序:",i+1);PrintArray(a, n);}
}int main()
{int a[] = { 3,6,4,2,11,10,5 };int n = sizeof(a) / sizeof(a[0]);BubbleSort(a, n);return 0;
}

冒泡排序算法时间复杂度及稳定性分析

时间复杂度分析:

由图可知:冒泡排序总共需要排(n-i)趟,每趟需要排(n-i)次;所以时间复杂度为:
(N-i)*(N-i),即O(N^2).

稳定性分析:
因为冒泡排序,是前一个数和后一个数相比,如果大于就交换,小于等于,就不交换,所以冒泡排序稳定。

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

快速排序

快速排序基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。将区间按照基准值划分为左右两半部分的常见方式有:
1. hoare版本–左右指针法
2. 挖坑法
3. 前后指针版本

注意:基准值一般选取最左边元素或是最右边元素
1. hoare版本–左右指针法

选出一个基准量keyi,一般选最左边数的下标的或是最右边数的下标。
1、右指针right向左走,找比key小的数,找到后停下,
2、左指针left向右走,找比key大的数,找到后停下。
3、此时,交换左右指针所指向的数
4、接下重复1、2步骤。直至左右指针相遇
5、左右指针相遇后,交换keyi的值和left指向的值。
6、此时,单趟排序结束。以原keyi的值,将数组分为两个子区间,每个子区间重复步骤1、2、3、4、5、6,直至整个数组有序。

注意:选最左边数作为基准值,让右指针先走,选最右边数作为基准值,让左指针先走。

2. 挖坑法
1、选择最左边数或是最右边数作为key,此时被选走的数就留下一个坑(hole)。
2、选左边的数做key,让右指针先走,找小于key的数,放入坑中,此时右指针成为新的坑。左指针再走,找大于key的数,放入新的坑中。再右指针找比key小的数…直至,左右指针相遇,将key放入坑中,第一趟排序结束。
3、再对第一趟所选的key的左右区间递归行排序,直至整个数组有序。

3、前后指针法
cur跟prev开始一前一后,cur去找比keyi位置小的值,找到以后,++prev,再交换prev和cur位置的值,直到数组尾,最后交换prev和keyi对应的值。一趟排序结束。

快速排序三种方法代码实现

1. hoare版本–左右指针法

#pragma once
#include <stdio.h>
#include<stdlib.h>//打印
void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) >> 1;if (a[left] < a[mid]){//a[left]<a[mid]<a[right]if (a[mid] < a[right])  //mid在中间{return mid;}//a[right]<a[left]<a[mid]else if (a[left] > a[right])  //right最小{return left;}//a[left]<a[right]<a[mid]else{return right;}}else{//a[right]<a[mid]<a[left]if (a[mid] > a[right]){return mid;}//a[mid]<a[right]<a[left]else if (a[right] < a[left]){return right;}//a[mid]<a[right]<a[left]else{return left;}}}
// 快排  双指针法
// hoare版本 -- 左右指针法
int PartSort1(int* a, int left, int right)
{// 三数取中int midIndex = GetMidIndex(a, left, right);Swap(&a[left], &a[midIndex]);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[keyi], &a[left]);return left;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort1(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int a[] = { 6,1,2,7,9,3,4,5,10,8 };int n = sizeof(a) / sizeof(a[0]);QuickSort(a, 0, n - 1);PrintArray(a, n);return 0;
}

2. 挖坑法

#pragma once
#include <stdio.h>
#include<stdlib.h>//打印
void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) >> 1;if (a[left] < a[mid]){if (a[mid] < a[right])  //mid在中间{return mid;}else if (a[left] > a[right])  //mid最小{return left;}else{return right;}}else{if (a[mid] > a[right]){return mid;}else if (a[right] < a[left]){return right;}else{return left;}}}// 挖坑法
int PartSort2(int* a, int left, int right)
{int midIndex = GetMidIndex(a, left, right);Swap(&a[left], &a[midIndex]);int key = a[left];while (left < right){// 找小while (left < right && a[right] >= key){--right;}// 放到左边的坑位中,右边就形成新的坑a[left] = a[right];// 找大while (left < right && a[left] <= key){++left;}// 放到右边的坑位中,左边就形成新的坑a[right] = a[left];}a[left] = key;return left;
}
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort2(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int a[] = { 6,1,2,7,9,3,4,5,10,8 };int n = sizeof(a) / sizeof(a[0]);QuickSort(a, 0, n - 1);PrintArray(a, n);return 0;
}


3、前后指针法

#pragma once
#include <stdio.h>
#include<stdlib.h>//打印
void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) >> 1;if (a[left] < a[mid]){if (a[mid] < a[right])  //mid在中间{return mid;}else if (a[left] > a[right])  //mid最小{return left;}else{return right;}}else{if (a[mid] > a[right]){return mid;}else if (a[right] < a[left]){return right;}else{return left;}}}// 前后指针法
int PartSort3(int* a, int left, int right)
{//int midIndex = GetMidIndex(a, left, right);//Swap(&a[left], &a[midIndex]);int keyi = left;int prev = left, cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}++cur;}Swap(&a[keyi], &a[prev]);return prev;
}
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int a[] = { 6,1,2,7,9,3,4,5,10,8 };int n = sizeof(a) / sizeof(a[0]);QuickSort(a, 0, n - 1);PrintArray(a, n);return 0;
}

快速排序算法时间复杂度及稳定性分析

时间复杂度分析:

稳定性分析:

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

归并排序

归并排序基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

归并排序代码实现

#pragma once
#include<stdlib.h>
#include<stdio.h>
// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (left + right) >> 1;// [left, mid][mid+1,right]_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);// 两段有序子区间归并tmp,并拷贝回去int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int i = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[i++] = a[begin1++];elsetmp[i++] = a[begin2++];}while (begin1 <= end1)tmp[i++] = a[begin1++];while (begin2 <= end2)tmp[i++] = a[begin2++];// 归并完成以后,拷贝回到原数组for (int j = left; j <= right; ++j)a[j] = tmp[j];
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail\n");exit(-1);}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}int main()
{int a[] = { 10,6,7,1,3,9,4,2 };int n = sizeof(a) / sizeof(a[0]);MergeSort( a, n);PrintArray(a, n);return 0;
}

归并排序算法时间复杂度及稳定性分析

时间复杂度分析:

稳定性分析:

归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

计数排序

计数排序基本思想
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

绝对映射:
1、统计数组a中的最大值max,根据最大值,开辟计数数组空间count,
int* count=(int*)malloc(sizeof(int)*(max+1))
2、统计出数组a中每个数出现的次数,出现一次就在
count数组中对应出现数的下标的位置+1,
for(int i=0;i<n;i++)
count[a[i]]++;

绝对映射


相对映射

计数排序代码实现

#pragma once
#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>//打印
void PrintArray(int* a, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}
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* count = (int*)malloc(sizeof(int) * range);memset(count, 0, sizeof(int) * range);for (int i = 0; i < n; ++i){count[a[i] - min]++;}int i = 0;for (int j = 0; j < range; ++j){while (count[j]--){a[i++] = j + min;}}free(count);
}int main()
{int a[] = { 2,5,3,0,2,3,0,3 };int n = sizeof(a) / sizeof(a[0]);CountSort(a, n);PrintArray(a, n);return 0;
}

计数排序算法时间复杂度分析

时间复杂度分析:

计数排序的特性总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)
  4. 稳定性:稳定

八大排序“华山论剑”,谁能称帝

排10万个数,看各大排序的性能,计数排序局限性太大,这里不予考虑

#define _CRT_SECURE_NO_WARNINGS
#include "Sort.h"void TestOP()
{srand(time(0));const int N = 100000;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);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];}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(a5, 0, N - 1);int end5 = clock();int begin6 = clock();MergeSort(a6, N);int end6 = clock();int begin7 = clock();CountSort(a7, N);int end7 = clock();int begin8 = clock();BubbleSort(a8, N);int end8 = 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);printf("CountSort:%d\n", end7 - begin7);printf("BubbleSort:%d\n", end8 - begin8);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);free(a8);
}int main()
{TestOP();return 0;
}


去掉几个时间复杂度是O(N^2)的排序,排100万个数
排1000万个数

从图综合看出,快速排序是最优的排序,归并排序要开辟O(N)的空间,综合来说不如快速排序。

总结


至此,整个排序算法终于结束!欢迎来讨论

“手把手”教你C语言八大排序相关推荐

  1. C语言八大排序算法,附动图和详细代码解释!

    文章来源:电子工程专辑.C语言与程序设计.竹雨听闲 一.前言 如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二. ...

  2. 硬核!C语言八大排序算法,附动图和详细代码解释!

    来源 :C语言与程序设计.竹雨听闲等 一 前言 如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二 八大排序算法 ...

  3. 序列划分c语言,一篇“get”C语言八大排序算法

    如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二.八大排序算法 排序算法作为数据结构的重要部分,系统地学习一下是 ...

  4. c语言的八大排序算法,程序员的内功:C语言八大排序算法

    四 一.冒泡排序 冒泡排序算法的运作如下: ●比较相邻的元素.如果第一个比第二个大,就交换他们两个. ●对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最后的元素会是最大的数. ...

  5. [小项目]手把手教你C语言哈夫曼压缩/解压缩

    前言 这是大一写过的一个小项目,现在大三,重新实现了一下.这是原来的链接,可以看一下效果,思路和现在的一样. 扩展性.健壮性比原来更好,思路也更清晰了.当时只想花里胡哨,这次重心放在质量.功能上. 后 ...

  6. (C语言)八大排序之:堆排序、快速排序

    堆排序( heap sort )  reference:http://mp.weixin.qq.com/s/mY_bVJPWhzZWL5ZdooA6tw 1 /* FILE: heapSort.c2 ...

  7. (C语言)八大排序之:插入排序、冒泡排序、选择排序

    1 /* 2 * FILE: p198_sort.c3 * DATE: 201801144 * --------------5 */6 7 #include <stdio.h>8 9 /* ...

  8. C语言八大排序算法【详细 代码+图文】

    这里写目录标题 1.冒泡排序 1.1 冒泡排序的思想 1.2 冒泡排序的实现 2.选择排序 2.1 选择排序的思想 2.2 选择排序的实现 3.直接插入排序 3.1 直接插入排序的思想 3.2 直接插 ...

  9. C语言--八大排序之直接插入排序算法

    排序:把无序的数据变得有序,默认升序.(笔试面试排名第一的内容) 1.直接(简单)插入排序(例如:扑克牌发牌时,每发一张,将牌有序插入) 从当前位置开始, 从后往前找比当前数字小的, 找到后插入到这个 ...

最新文章

  1. 简单几步,教你部署一个可扩展微服务系统!
  2. 如果你也会C#,那不妨了解下F#(6):面向对象编程之“类”
  3. 【Python学习】 - TensorFlow.keras 不显示epochs进度条的方法
  4. 谐波分析显著性检验matlab,关于谐波检测方法的MATLAB仿真及综合性能分析.pdf
  5. PowerDesigner生成数据库设计文档
  6. 如何使用利用LaTeX制作个人简历
  7. mybatis多种方式注册映射文件
  8. PHP手册总结《预定义变量》
  9. 哈工大SCIR Lab | EMNLP 2019 结合单词级别意图识别的stack-propagation框架进行口语理解...
  10. jdbc-日期格式的转换及代码示例
  11. HttpSendRequest向服务端发送数据,构造请求http头
  12. pytesseract识别数字
  13. 最棒的Java开发企业级权限管理系统项目实战
  14. 《About Face 3:交互设计精髓》读书笔记(一)
  15. 第一台计算机英语句子,20个虚拟语气句子带翻译
  16. OpenCV python 绘制椭圆形
  17. Window7 Chrome 升级提示关闭
  18. 2018年sfdc工作总结_常见Salesforce 异常
  19. oracle使用with递归查询
  20. 开源的虚拟化私有云及云管平台

热门文章

  1. java基础-IO编程
  2. html5视频画质清晰度切换和倍速播放切换代码参考
  3. oracle3节点漂移,10.2.0.3 VIP 漂移问题
  4. 我的Direct3D之路2:学习第一个三角形绘制程序以及一些注意点
  5. asp.net下用AspNetPager分页控件对DataList进行分页
  6. NR/5G - Type 1 codebook repetition配置时一个理解
  7. 安装linux版的skype
  8. device_create 函数详细分析
  9. 11个最佳社交媒体脚本和插件可简化您的工作流程
  10. 学生信息统计(链表)