【数据结构】排序

在这里插入图片描述

🐇

🔥博客主页: 云曦
📋系列专栏:数据结构

💨吾生也有涯,而知也无涯
💛 感谢大家👍点赞 😋关注📝评论

文章目录

  • 前言
  • 一、排序的概念及运用
  • 二、常见排序算法的实现
    • 📙2.1 插入排序
      • 📓2.1.1 直接插入排序
      • 2.1.2 📓希尔排序
    • 📙2.2 选择排序
      • 📓2.2.1 直接选择排序
      • 📓2.2.2 堆排序
    • 📙2.3 交换排序
      • 📓2.3.1 冒泡排序
      • 📓2.3.2 快速排序
        • 📄2.3.2.1 hoare版本
        • 📄2.3.2.2 挖坑法
        • 📄2.3.2.3 前后指针版本
        • 📄2.3.2.4 快速排序(非递归实现)
    • 📙2.4 归并排序
      • 📓2.4.1 归并排序
      • 📓2.4.2 归并排序(非递归实现)
    • 📙2.5 非比较排序
      • 📓2.5.1 计数排序
  • 三、 排序算法的复杂度和稳定性
  • 四、本篇章的代码
    • 📙4.1 **<font color=Orange>Sort.h**
    • 📙4.2 **<font color=Orange>Sort.c**
    • 📙4.3 **<font color=Orange>Test.c**

前言

在上期我们讲解完了普通二叉树,这期又是一期新的知识点(排序)。

一、排序的概念及运用

  • 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作
  • 排序的运用:
    排序在生活中运用的场景很多,例如:我们想知道一部手机的价格、性能、品牌等信息的排名时,排序的作用就发挥出来了
    在这里插入图片描述

二、常见排序算法的实现

在这里插入图片描述

📙2.1 插入排序

📓2.1.1 直接插入排序

  • 插入排序是一种很简单的排序法,其思想就是:
  • 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
  • 其次排序与之前的数据结构不同,我们实现的时候一般先实现单趟排序,再去实现多趟排序。
  • 单趟的思路:
  1. 定义临时变量(tmp)来保存要插入的数据
  2. 然后与前面的数比较,大于tmp就往后挪
  3. 小于tmp,就把tmp赋值给这个元素的后一位
    在这里插入图片描述
  • 单趟实现:
void InsetionSort(int* arr, int n)
{int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];}else{break;}end--;}arr[end + 1] = tmp;}
  • 多趟思路:
  • 在套一层循环
  • 需要注意的是:end最后落到的位置是n-2的位置,n-1的话会把下标为n的数据插入进去导致越界访问
void InsetionSort(int* arr, int n)
{int i = 0;//end最后停留的位置是n-2的位置//所以i要小于n-1for (i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];}else{break;}end--;}arr[end + 1] = tmp;}}
  • 复杂度:
  1. 时间复杂度:
    最坏情况(要升序,而数组是逆序):O(N^2)
    最好情况(要升序,而数组是顺序有序):O(N)
  2. 空间复杂度:O(1)

2.1.2 📓希尔排序

  • 希尔排序是在直接插入排序的基础上变形的一种排序。
  • 希尔排序分为:
  1. 预排序
    预排序是分组排序,间隔gap的为一组,
  2. 直接插入排序
  • 预排序的意义:大的数更快的到后面,小的数更快的到前面,
    gap越大跳得越快,越不接近有序
    gap越小跳得越慢,越接近有序,当gap==1时,就是直接插入排序
    但gap == 1时,就是直接插入排序了
    在这里插入图片描述
  • 单组gap的单次执行思路:
  1. 首先假设gap==3,end从下标0开始,tmp记录end+gapx位置的元素
  2. 然后判断tmp记录的元素是否小于end位置的元素,小于就把end位置的元素往后挪到end+gap的位置上,大于就break跳出循环,叫tmp赋值给end+gap的位置上
  • 这个思路就类似直接插入排序,就是把加1的位置变成了加gap
void ShellSort(int* arr, int n)
{int gap = 3;int end = 0;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}
  • 单组gap的执行思路:

上面的过程,只是执行了一组内一次的过程,要一组都执行完的话,还要加一层循环

  1. 定义i从0开始小于n-gap,每次循环上来减等gap
void ShellSort(int* arr, int n)
{int gap = 3;int i = 0;for (i = 0; i < n - gap; i += gap){int end = 0;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
  • gap组的执行思路:
  • gap组执行,简单嘛,再加一层循环
    循环从0开始,小于n结束
void ShellSort(int* arr, int n)
{int gap = 3;int j = 0;for (j = 0; j < n; j++){int i = j;for (i = j; i < n - gap; i += gap){int end = 0;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}}
  • 上面这部分逻辑有的地方是这样写的:
  1. 多组并排,也就是多组一起排,end最后落的位置是n-gap的位置
    这种写法的效率和上面的效率是一样的,只是思路不一样而已
    在这里插入图片描述
void ShellSort(int* arr, int n)
{int gap = 3;int i = 0;for (i = 0; i < n - gap; ++i){int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}
  • gap的取值
  • 那么问题来了,gap应该取多少合适呢?
    答案是:把n赋值给gap,然后gap = gap / 2,除2的数可以保证最后一次gap永远是1,就是直接插入排序。
    有人决定gap/2比较慢,就改成了gap/3,也是可以的,但gap/3时要注意一点:gap小于3或其他数时,会除出0来,所以要写成gap/3+1
void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){gap = gap / 2;//gap = gap / 3 + 1;int i = 0;for (i = 0; i < n - gap; ++i){int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}}
  • 测试:
void TestShellSort()
{int arr[] = { 9,1,2,5,7,4,8,6,3,5 };int size = sizeof(arr) / sizeof(arr[0]);InsertSort(arr, size);PrintArray(arr, size);
}

在这里插入图片描述

  • 复杂度:
  1. gap/2的话,时间复杂度:O(log₂N)
    gap/3+1的话,时间复杂度:O(log₃N)
    也有些地方通过计算得出时间复杂度:O(N^1.3)
  2. 空间复杂度:O(1)

📙2.2 选择排序

📓2.2.1 直接选择排序

  • 直接插入排序是一个很简单的排序
  1. 遍历找出最小和最大值的下标,然后小的和左边的数交换,大的和右边的数交换
    在这里插入图片描述
  1. 先找出最大值和最小值的下标
void SelectSort(int* arr, int n)
{int mini = begin;int maxi = begin;int i = 0;for (i = begin+1; i < end; i++){if (arr[i] > arr[maxi]){maxi = i;}if (arr[i] < arr[mini]){mini = i;}}}
  1. 套上循环,左边从0开始,右边从n-1开始,形成左闭右闭
    每次找到最大和最小的值和左右边上交换,然后左加1,右-1
  • 需要注意的是:maxi和begin有可能在一个位置上,begin和mini先交换,有可能会导致maxi位置上的值变成其他的,这时我们要加上一个判断进行修正
void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){//[begin,end] 左闭右闭int mini = begin;int maxi = begin;int i = 0;for (i = begin+1; i < end; i++){if (arr[i] > arr[maxi]){maxi = i;}if (arr[i] < arr[mini]){mini = i;}}Swap(&arr[begin], &arr[mini]);//max如果被换走,就修正一下if (maxi == begin){maxi = mini;}Swap(&arr[end], &arr[maxi]);++begin;--end;}}
  • 复杂度:
  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)

📓2.2.2 堆排序

堆排序在二叉树的篇章讲过了,就不在论述了,大家可以去此链接查看:
二叉树讲解堆排序的篇章

  • 复杂度
  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(1)

📙2.3 交换排序

📓2.3.1 冒泡排序

  • 冒泡排序就不用说了,相信大家在学习C语言的语法时,就已经学过了
  • 思路也就是两两比较,小于就交换
void BubbleSort(int* arr, int n)
{int j = 0;for (j = 0; j < n; j++){int flag = 0;int i = 1;for (i = 1; i < n-j; i++){if (arr[i - 1] > arr[i]){Swap(&arr[i - 1], &arr[i]);flag = 1;}}if (flag == 0){break;}}}
  • 复杂度:
  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)

📓2.3.2 快速排序

📄2.3.2.1 hoare版本
  • 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法
  • hoare版本的思想:
  • 单趟思想:
    右边先走,找比kay小的值
    右边找到后,左边再走,找比kay大的值,然后交换左右的值
    当L遇到R时,遇见位置的值一定比kay小,交换一下
    在这里插入图片描述
    在这里插入图片描述
int PartSort1(int* arr, int left, int right)
{int kay = arr[left];while (left < right){//找小while (arr[right] > kay){--right;}//找大while (arr[left] < kay){++left;}Swap(&arr[right], &arr[left]);}Swap(&arr[left], &kay);return left;
}
  • 这样单趟就写好了,不过还没写完,与几处地方有坑
  1. 坑1:会死循环
    在这里插入图片描述
int PartSort1(int* arr, int left, int right)
{int kay = arr[left];while (left < right){//找小while (arr[right] >= kay){--right;}//找大while (arr[left] <= kay){++left;}Swap(&arr[right], &arr[left]);}Swap(&arr[left], &kay);return left;
}
  1. 坑2:会导致越界访问
    在这里插入图片描述
int PartSort1(int* arr, int left, int right)
{int kay = left;while (left < right){//找小while (left < right && arr[right] >= kay){--right;}//找大while (left < right && arr[left] <= kay){++left;}Swap(&arr[right], &arr[left]);}Swap(&arr[left], &kay);return left;
}
  1. 坑3:交换时,没有交换到数组里的值
    在这里插入图片描述
int PartSort1(int* arr, int left, int right)
{int kayi = left;while (left < right){//找小while (left < right && arr[right] >= arr[kayi]){--right;}//找大while (left < right && arr[left] <= arr[kayi]){++left;}Swap(&arr[right], &arr[left]);}Swap(&arr[left], &arr[kayi]);return left;
}
  • 整体思想:
    在这里插入图片描述
void QuickSort(int* arr, int regin, int end)
{if (regin >= end){return;}int kayi = PartSort1(arr, regin, end);//左区间          分割下标  右区间//[regin,kayi-1]    kayi   [kayi+1,end]QuickSort(arr, regin, kayi - 1);//递归kayi的左区间QuickSort(arr, kayi + 1, end);//递归kayi的右区间
}
  • hoare版本的快排就写好了
  • 复杂度:
    时间复杂度:O(N*logN)
    最坏情况(排升序,而数组是升序),时间复杂度O(N^2)
    在这里插入图片描述
  • 快排有一个小的优化:
  • 三数取中:
    含义是:左边、中间、右边,三个数,取不是最大也不是最小的那个数的下标返回,返回后与left交换位置
  • 有三数取中的优化,快速排序不会出现最坏情况
int GetMidi(int* arr, int left, int right)
{int mid = (left + right) / 2;if (arr[left] < arr[mid]){if (arr[mid] < arr[right]){return mid;}else if (arr[right] > arr[left])//mid是最大值的下标{return right;}else{return left;}}else   //arr[left] > arr[mid]{if (arr[mid] > arr[right]){return mid;}else if(arr[right] < arr[left]) //mid是最小值的下标{return right;}else{return left;}}}
📄2.3.2.2 挖坑法

想来看看挖坑法的动图
在这里插入图片描述

  • 挖坑法单趟思想:
  1. kay记录左边数据,坑位也记录在左边
  2. 右边找小,找到后和坑位的位置进行交换,最后坑位更新到右边
  3. 再是左边找小,找到后和坑位的位置进行交换,最后坑位更新到左边
  4. 当相遇时,把kay保存的数据赋值到坑位的位置上,返回坑位(相遇时坑位是kay应该到的位置(排好序的位置上))
    在这里插入图片描述
int PartSort2(int* arr, int left, int right)
{int midi = GetMidi(arr, left, right);Swap(&arr[midi], &arr[left]);int kay = arr[left];int hole = left;while (left < right){//找小,找到后与坑位交换,右边形成新的坑while (left < right && arr[right] >= kay){--right;}Swap(&arr[right], &arr[hole]);hole = right;//找大,找到后与坑位交换,左边形成新的坑while (left < right && arr[left] <= kay){++left;}Swap(&arr[left], &arr[hole]);hole = left;}arr[hole] = kay;return hole;
}
  • 挖坑法整体思想:
    整体思想还是跟hoare版本的整体思想一样,用递归实现
void QuickSort(int* arr, int regin, int end)
{if (regin >= end){return;}int kayi = PartSort2(arr, regin, end);//左区间          分割下标  右区间//[regin,kayi-1]    kayi   [kayi+1,end]QuickSort(arr, regin, kayi - 1);//递归kayi的左区间QuickSort(arr, kayi + 1, end);//递归kayi的右区间
}
📄2.3.2.3 前后指针版本

先看前后指针版本的动图:
在这里插入图片描述

  • 前后指针版本的单趟思想:
  1. 用kayi记录左边的下标,prev从左边开始,cur从prev+1开始
  2. cur找小,找到就++prev然后交换
  3. 当cur >= right时(循环的结束条件),循环结束,把kayi位置的值和prev位置的值进行交换,然后返回prev
    在这里插入图片描述
int PartSort3(int* arr, int left, int right)
{int midi = GetMidi(arr, left, right);Swap(&arr[midi], &arr[left]);int prev = left;int cur = prev + 1;int kayi = left;while (cur <= right){if (arr[cur] <= arr[kayi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[kayi]);return prev;
}
  • 前后指针版本的整体思想:
    整体思想也是跟前面两个版本一样的,用递归实现
void QuickSort(int* arr, int regin, int end)
{if (regin >= end){return;}int kayi = PartSort2(arr, regin, end);//左区间          分割下标  右区间//[regin,kayi-1]    kayi   [kayi+1,end]QuickSort(arr, regin, kayi - 1);//递归kayi的左区间QuickSort(arr, kayi + 1, end);//递归kayi的右区间
}
  • 整体思想的一个优化
  • 大家都知道递归深度太深会导致栈溢出,所以当递归到10个数以内的区间后,可以用插入排序进行排序,这样就减少了递归的次数
void QuickSort(int* arr, int begin, int end)
{if (begin >= end){return;}//小区间优化,不在递归分割排序,降低了递归次数if (begin + end + 1 > 10){int kayi = PartSort3(arr, begin, end);//[begin, kayi-1] kayi [kayi+1, end]QuickSort(arr, begin, kayi - 1);QuickSort(arr, kayi + 1, end);}else{InsertSort(arr, begin + end + 1);}}
📄2.3.2.4 快速排序(非递归实现)

用递归排序正常的数据是可以排好的,但在一些特定的情况下,递归深度太深导致栈溢出,那么就需要改成非递归了

  • 非递归的快排思想:
  1. 非递归的快排,我们要借用栈的数据结构来实现
  2. 利用栈的先进后出原则,先入开始的区间,走单趟排序,接收kayi后,右区间先入栈左区间在入栈,出栈时,排序左区间,再排右区间
    在这里插入图片描述
void QuickSortNonR(int* arr, int begin, int end)
{ST st;STInit(&st);STPush(&st, end);STPush(&st, begin);while (!STEmpty(&st)){int left = STTop(&st);STPop(&st);int right = STTop(&st);STPop(&st);int kayi = PartSort3(arr, left, right);if (kayi + 1 <= right){STPush(&st, right);STPush(&st, kayi + 1);}if (left <= kayi - 1){STPush(&st, kayi - 1);STPush(&st, left);}}STDestroy(&st);
  • 总结:
  1. 快速排序的时间复杂度:O(N*logN)
  2. 快速排序的空间复杂度:O(logN)

📙2.4 归并排序

📓2.4.1 归并排序

先看图在这里插入图片描述

  • 归并排序就是分割出左右区间有序就归并,左右区间没有序就在分割左右区间,分割到只有一个数时那么一个数就是有序的把单个数归并到临时开辟的数组里,再拷贝回原数组,
  • 1个和1个的归并有序后再进行2个和2个的归并,然后再进行4个和4个的归并
    在这里插入图片描述
  • 思路为:
  1. 开辟一个和原数组一样大的临时空间
  2. 因为要用递归实现,所以要写一个子函数进行递归式归并
  • 归并排序
void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}_MergeSort(arr, tmp, 0, n-1);free(tmp);tmp = NULL;
}
  • 子函数的实现
void _MergeSort(int* arr, int* tmp, int begin, int end)
{if (begin >= end){return;}int mid = (begin + end) / 2;//[begin, mid] [mid+1, end] 区间的分割_MergeSort(arr, tmp, begin, mid);//递归左区间_MergeSort(arr, tmp, mid + 1, end);//递归右区间//[begin1, end1] [begin2, end2]int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int index = begin;//左右区间取小的尾插到tmp数组里while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}//如果begin1没有小于end1,那么就循环尾插到tmp数组里while (begin1 <= end1){tmp[index++] = arr[begin1++];}//如果begin2没有小于end2,那么就循环尾插到tmp数组里while (begin2 <= end2){tmp[index++] = arr[begin2++];}//拷贝到原数组里memcpy(arr+begin,tmp+begin,sizeof(int)*(end-begin+1));
}

📓2.4.2 归并排序(非递归实现)

  • 用递归写的代码,要有非递归版本,不然在一些特定的情况下递归深度太深会导致栈溢出
  • 归并排序的非递归思路:
  1. 开辟一个和原数组一样大小的空间
  2. gap从去开始(11归并、22归并、44归并等)
  • 单趟思路:
  1. 循环 i 控制每次循环上来i += 2 * gap
  2. 这里需要注意的是区间的控制:
    在这里插入图片描述
  3. 归并到tmp数组里了后,把数据拷贝回原数组里
void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;int i = 0;for (i = 0; i < n; i += 2 * gap){//[begin1, end1] [begin2, end2]int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int index = begin;//左右区间取小的尾插到tmp数组里while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}//如果begin1没有小于end1,那么就循环尾插到tmp数组里while (begin1 <= end1){tmp[index++] = arr[begin1++];}//如果begin2没有小于end2,那么就循环尾插到tmp数组里while (begin2 <= end2){tmp[index++] = arr[begin2++];}//拷贝回原数组里memcpy(arr+i,tmp+i,sizeof(int) * (end2-i+1));}free(tmp);tmp = NULL;
}
  • 整体思路:
    在加一层循环,条件为gap<n,里面执行结束后,gap *= 2,这样就可以11归并、22归并、44归并了
void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;while (gap < n){int i = 0;for (i = 0; i < n; i += 2 * gap){//[begin1, end1] [begin2, end2]int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int index = i;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}memcpy(arr+i,tmp+i,sizeof(int)*(end2-i+1));}gap *= 2;}free(tmp);tmp = NULL;
}
  • 这样归并排序就实现好了,当还没完全实现完
  • 8个数据没有出现问题,当9个数据或10个数据时会有越界访问的问题
    在这里插入图片描述
  • 具体是begin1、end1、begin2、end2哪个区间有越界我们不知道,那么就打印一下区间来看看
    在这里插入图片描述
  • 知道了是end1、begin2、end2或begin2、end2或end2在些区间会出现越界后,就加上判断即可
  1. 判断begin2是否大于等于n,是就break跳出循环,不归并这组数
  2. 如果只是end2大于等于n,那么修正一下把end2改为n-1即可
void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;while (gap < n){int i = 0;for (i = 0; i < n; i += 2 * gap){//[begin1, end1] [begin2, end2]int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int index = i;//如果第二组不存在,那么这一组就不用归并了if (begin2 >= n) {break;}//最右边不存在的话,修正一下if (end2 >= n){end2 = n - 1;}//printf("[%d][%d][%d][%d] ", \begin1, end1, begin2, end2);while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}memcpy(arr+i,tmp+i,sizeof(int)*(end2-i+1));}//printf("\n");gap *= 2;}free(tmp);tmp = NULL;
}
  • 总结:
  1. 归并排序的时间复杂度:O(N*logN)
  2. 归并排序的空间复杂度:O(N)

📙2.5 非比较排序

📓2.5.1 计数排序

  1. 计算排序是通过一个临时数组统计每个数据出现的次数
  2. 然后通过个数覆盖到原数组里
    在这里插入图片描述
    这种方法称为映射,上面那种是绝对映射,还有一种是相对映射
    在这里插入图片描述

思路:

  1. 遍历找出最大和最小值
  2. 计算出开辟空间的大小,然后开辟空间命名为count
  3. 用原数组的数据减去最小值,然后在count数组里统计出现的次数
  4. 把count数组统计的次数依次覆盖到原数组里
void CountSort(int* arr, int n)
{int max = arr[0];int min = arr[0];//找最大和最小值int i = 0;for (i = 1; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");exit(-1);}memset(count, 0, sizeof(int) * range);//初始化为0//统计数据出现的次数for (i = 0; i < n; i++){count[arr[i] - min]++;}//排序,依次取出覆盖到原数组里int j = 0;for (i = 0; i < range; i++){while (count[i]--){arr[j++] = i + min;}}}
  • 总结:
  1. 计数排序的时间复杂度:O(N + range)
  2. 计数排序的空间复杂度:O(range)

三、 排序算法的复杂度和稳定性

时间复杂度空间复杂度稳定性不稳定的原因
插入排序O(N^2)O(1)稳定------------
希尔排序O(N^1.3)O(1)不稳定预排序时,相同的数据可能分在不同的组里
选择排序O(N^2)O(1)不稳定!不稳定的情况
堆排序O(N*logN)O(1)不稳定!不稳定的情况
冒泡排序O(N^2)O(1)稳定------------
快速排序O(N*logN)O(logN)不稳定!不稳定的情况
归并排序O(N*logN)O()N稳定------------

四、本篇章的代码

📙4.1 Sort.h

#pragma once
#include<stdio.h>
#include<string.h>//打印函数
void PrintArray(int* arr, int n);
//直接插入排序
void InsertSort(int* arr, int n);
//希尔排序
void ShellSort(int* arr, int n);
//堆排序
void HeapSort(int* arr, int n);
//冒泡排序
void BubbleSort(int* arr, int n);
//直接选择排序
void SelectSort(int* arr, int n);
//快速排序
void QuickSort(int* arr, int begin, int end);
//快速排序 非递归
void QuickSortNonR(int* arr, int begin, int end);
//归并排序
void MergeSort(int* arr, int n);
//归并排序 非递归
void MergeSortNonR(int* arr, int n);
//计数排序
void CountSort(int* arr, int n);

📙4.2 Sort.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Sort.h"
#include"Stack.h"void PrintArray(int* arr, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void InsertSort(int* arr, int n)
{int i = 0;for (i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];}else{break;}--end;}arr[end + 1] = tmp;}}void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){//gap = gap / 2;gap = gap / 3 + 1;int i = 0;for (i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}}void AdjustDown(int* arr, int n, int parent)
{int child = parent * 2 + 1;while (child < n){//找小的孩子if (child+1 < n && arr[child + 1] > arr[child]){++child;}if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void HeapSort(int* arr, int n)
{//向下调整,建堆int i = 0;for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, n, i);}//排序int end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);--end;}}void BubbleSort(int* arr, int n)
{int j = 0;for (j = 0; j < n; j++){int flag = 0;int i = 0;for (i = 1; i < n - j; i++){if (arr[i - 1] > arr[i]){Swap(&arr[i - 1], &arr[i]);flag = 1;}}if (flag == 0){break;}}
}void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin <= end){//[begin, end] 左闭右闭区间int mini = begin;int maxi = begin;int i = 0;for (i = begin; i < end; i++){if (arr[i] > arr[maxi]){maxi = i;}if (arr[i] < arr[mini]){mini = i;}}Swap(&arr[begin], &arr[mini]);//max如果被替换了,修正一下if (maxi == begin){maxi = mini;}Swap(&arr[end], &arr[maxi]);++begin;--end;}}//三数取中
int GetMidi(int* arr, int left, int right)
{int mid = (left + right) / 2;if (arr[mid] > arr[left]){if (arr[mid] < arr[right]){return mid;}else if (arr[left] > arr[right]) //mid是最大值的下标{return left;}else{return right;}}else //arr[mid] < arr[left]{if (arr[mid] > arr[right]){return mid;}else if (arr[left] < arr[right]) //mid是最小值的下标{return left;}else{return right;}}}//hoare版本
int PartSort1(int* arr, int left, int right)
{int midi = GetMidi(arr, left, right);Swap(&arr[midi], &arr[left]);int kayi = left;while (left < right){//找小while (left < right && arr[right] >= arr[kayi]){--right;}//找大while (left < right && arr[left] <= arr[kayi]){++left;}Swap(&arr[left], &arr[right]);}Swap(&arr[left], &arr[kayi]);return left;
}//挖坑法
int PartSort2(int* arr, int left, int right)
{int midi = GetMidi(arr, left, right);Swap(&arr[midi], &arr[left]);int kay = arr[left];int hole = left;while (left < right){//找小,找到后与坑位交换,右边形成新的坑while (left < right && arr[right] >= kay){--right;}Swap(&arr[right], &arr[hole]);hole = right;//找大,找到后与坑位交换,左边形成新的坑while (left < right && arr[left] <= kay){++left;}Swap(&arr[left], &arr[hole]);hole = left;}arr[hole] = kay;return hole;
}//前后指针版本
int PartSort3(int* arr, int left, int right)
{int midi = GetMidi(arr, left, right);Swap(&arr[midi], &arr[left]);int prev = left;int cur = prev + 1;int kayi = left;while (cur <= right){if (arr[cur] <= arr[kayi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[kayi]);return prev;
}void QuickSort(int* arr, int begin, int end)
{if (begin >= end){return;}//小区间优化,不在递归分割排序,降低了递归次数if (begin + end + 1 > 10){int kayi = PartSort3(arr, begin, end);//[begin, kayi-1] kayi [kayi+1, end]QuickSort(arr, begin, kayi - 1);QuickSort(arr, kayi + 1, end);}else{InsertSort(arr, begin + end + 1);}}void QuickSortNonR(int* arr, int begin, int end)
{ST st;STInit(&st);STPush(&st, end);STPush(&st, begin);while (!STEmpty(&st)){int left = STTop(&st);STPop(&st);int right = STTop(&st);STPop(&st);int kayi = PartSort3(arr, left, right);if (kayi + 1 <= right){STPush(&st, right);STPush(&st, kayi + 1);}if (left <= kayi - 1){STPush(&st, kayi - 1);STPush(&st, left);}}STDestroy(&st);
}void _MergeSort(int* arr, int* tmp, int begin, int end)
{if (begin >= end){return;}int mid = (begin + end) / 2;//[begin, mid] [mid+1, end]_MergeSort(arr, tmp, begin, mid);_MergeSort(arr, tmp, mid + 1, end);//[begin1, end1] [begin2, end2]int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int index = begin;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}//拷贝到原数组里memcpy(arr+begin,tmp+begin,sizeof(int)*(end-begin+1));
}void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}_MergeSort(arr, tmp, 0, n-1);free(tmp);tmp = NULL;
}void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;while (gap < n){int i = 0;for (i = 0; i < n; i += 2 * gap){//[begin1, end1] [begin2, end2]int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int index = i;//如果第二组不存在,那么这一组就不用归并了if (begin2 >= n) {break;}//最右边不存在的话,修正一下if (end2 >= n){end2 = n - 1;}//printf("[%d][%d][%d][%d] ", \begin1, end1, begin2, end2);while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));}//printf("\n");gap *= 2;}free(tmp);tmp = NULL;
}//时间复杂度:O(N + range)
//空间复杂度:O(range)
void CountSort(int* arr, int n)
{int max = arr[0];int min = arr[0];//找最大和最小值int i = 0;for (i = 1; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");exit(-1);}memset(count, 0, sizeof(int) * range);//初始化为0//统计数据出现的次数for (i = 0; i < n; i++){count[arr[i] - min]++;}//排序int j = 0;for (i = 0; i < range; i++){while (count[i]--){arr[j++] = i + min;}}}

📙4.3 Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Sort.h"void TestInsertSort()
{int arr[] = { 9,5,3,7,2,8,1,4,5,6,10 };int size = sizeof(arr) / sizeof(arr[0]);InsertSort(arr, size);PrintArray(arr, size);
}void TestShellSort()
{int arr[] = { 9,1,2,5,7,4,8,6,3,5 };int size = sizeof(arr) / sizeof(arr[0]);ShellSort(arr, size);PrintArray(arr, size);
}void TestHeapSort()
{int arr[] = { 9,1,2,5,7,4,8,6,3,5 };int size = sizeof(arr) / sizeof(arr[0]);HeapSort(arr, size);PrintArray(arr, size);
}void TestBubbleSort()
{int arr[] = { 9,1,2,5,7,4,8,6,3,5 };int size = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr, size);PrintArray(arr, size);
}void TestSelectSort()
{int arr[] = { 9,1,2,5,7,4,8,6,3,5 };int size = sizeof(arr) / sizeof(arr[0]);SelectSort(arr, size);PrintArray(arr, size);
}void TestQuickSort()
{int arr[] = {6,1,2,7,9,3,4,5,10,8 };int size = sizeof(arr) / sizeof(arr[0]);QuickSort(arr, 0, size - 1);PrintArray(arr, size);
}void TestQuickSortNonRSort()
{int arr[] = { 6,1,2,7,9,3,4,5,10,8 };int size = sizeof(arr) / sizeof(arr[0]);QuickSortNonR(arr, 0, size-1);PrintArray(arr, size);
}void TestMergeSort()
{int arr[] = { 10,6,7,1,3,9,4,2 };int size = sizeof(arr) / sizeof(arr[0]);MergeSort(arr, size);PrintArray(arr, size);
}void TestMergeSortNonR()
{int arr[] = { 6,7,1,3,9,4,2,5,9,1,1 };int size = sizeof(arr) / sizeof(arr[0]);MergeSortNonR(arr, size);PrintArray(arr, size);
}void TestCountSort()
{int arr[] = { 6,7,1,3,9,4,2,5,9,1,1 };int size = sizeof(arr) / sizeof(arr[0]);CountSort(arr, size);PrintArray(arr, size);
}int main()
{//TestInsertSort();//TestShellSort();//TestHeapSort();//TestBubbleSort();//TestSelectSort();//TestQuickSort();//TestQuickSortNonRSort();//TestMergeSort();//TestMergeSortNonR();TestCountSort();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/126026.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Swift SwiftUI CoreData 过滤数据 1

Xcode: Version 14.3.1 (14E300c) iOS: 16 预览&#xff1a; Code: import SwiftUI import CoreDatastruct TodosSearch: View {State private var search_title "测试"FetchRequest var todos_search: FetchedResults<Todo>init() {let request: NSFetchReq…

【linux进程(二)】如何创建子进程?--fork函数深度剖析

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; 进程状态管理 1. 前言2. 查看…

Gmail 将停止支持基本 HTML 视图

根据 Google 支持文档的更新内容&#xff0c;Gmail 将从明年 1 月起停止支持基本 HTML 视图。 ▲ Gmai 基本 HTML 视图界面 目前网页版 Gmail 提供两个界面&#xff1a;基本 HTML 视图和标准视图。停止支持基本 HTML 视图后&#xff0c;当前打开经典模式的基本 HTML 视图模式 …

JavaScript系列从入门到精通系列第十七篇:JavaScript中的全局作用域

文章目录 前言 1&#xff1a;什么叫作用域 一&#xff1a;全局作用域 1&#xff1a;全局变量的声明 2&#xff1a;变量声明和使用的顺序 3&#xff1a;方法声明和使用的顺序 前言 1&#xff1a;什么叫作用域 可以起作用的范围 function fun(){var a 1; } fun();consol…

light client轻节点简介

1. 引言 前序博客&#xff1a; Helios——a16z crypto构建的去中心化以太坊轻节点 去中心化和自我主权对于Web3的未来至关重要,但是这些理想并不总适用于每个项目或应用程序。在非托管钱包和bridges等工具中严格优先考虑安全性而不是便利性的用户&#xff0c;可选择运行全节…

苹果mac电脑securecrt下载 附securecrt破解文件

SecureCRT for Mac是一款由VanDyke Software公司开发的终端仿真软件&#xff0c;专为Mac OS X系统设计&#xff0c;用于提供安全SSH、Telnet和其他协议的远程访问和管理。它适用于各种操作系统和设备&#xff0c;如Windows、Linux和UNIX等&#xff0c;为Mac用户提供了广泛的连接…

黑豹程序员-架构师学习路线图-百科:JSON替代XML

文章目录 1、数据交换之王2、XML的起源3、JSON诞生4、什么是JSON 1、数据交换之王 最早多个软件之间使用txt进行信息交互&#xff0c;缺点&#xff1a;纯文本&#xff0c;无法了解其结构&#xff1b;之后使用信令&#xff0c;如&#xff1a;电话的信令&#xff08;拨号、接听、…

【C语言】利用数组处理批量数据(字符数组)

前言:前面已经介绍了&#xff0c;字符数据是以字符的ASCII代码存储在存储单元中的&#xff0c;一般占一个字节。由于ASCII代码也属于整数形式&#xff0c;因此在C99标准中&#xff0c;把字符类型归纳为整型类型中的一种。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x…

makeMakefile

一、 什么是make&Makefile &#xff1f; ①make 是一条命令,makefile是一个文件,配合使用,通过依赖关系和依赖方法达到我们形成可执行程序的目的 ②makefile好处就是可以进行 自动化编译 ” &#xff0c;极大的提高软件开发的效率,一旦写好&#xff0c;只需要一个 make 命令…

手机图片合成gif怎么操作?用这个网站试试

制作gif动图的工具越来越多&#xff0c;但是很多时候使用电脑并不方便&#xff0c;想要在手机上制作gif动图的时候应该怎么办呢&#xff1f;很简单&#xff0c;给大家分享一款无需下载手机浏览器就能操作的gif制作&#xff08;https://www.gif.cn/&#xff09;工具-GIF中文网&a…

【AI视野·今日Robot 机器人论文速览 第四十七期】Wed, 4 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Wed, 4 Oct 2023 Totally 40 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;基于神经网络的多模态触觉感知, classification, position, posture, and force of the grasped object多模态形象的解耦(f…

OpenCV利用Camshift实现目标追踪

目录 原理 做法 代码实现 结果展示 原理 做法 代码实现 import numpy as np import cv2 as cv# 读取视频 cap cv.VideoCapture(video.mp4)# 检查视频是否成功打开 if not cap.isOpened():print("Error: Cannot open video file.")exit()# 获取第一帧图像&#x…