【数据结构】排序

在这里插入图片描述

🐇

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

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

文章目录

  • 前言
  • 一、排序的概念及运用
  • 二、常见排序算法的实现
    • 📙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.rhkb.cn/news/152324.html

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

相关文章

基于vue框架的uniapp小程序开发发现了新大陆

项目场景&#xff1a; 在基于vue框架的uniapp小程序开发中&#xff0c;在页面跳转时&#xff0c;当前页路径带参数&#xff0c;在跳转页中接受数据除了用官方推荐的保留当前页面&#xff0c;跳转到应用内的某个页面&#xff0c;使用onLoad(option)接受数据&#xff0c;但是我发…

TensorFlow入门(十四、数据读取机制(1))

TensorFlow的数据读取方式 TensorFlow的数据读取方式共有三种,分别是: ①预加载数据(Preloaded data) 预加载数据的方式,其实就是静态图(Graph)的模式。即将数据直接内嵌到Graph中,再把Graph传入Session中运行。 示例代码如下: import tensorflow.compat.v1 as tf tf.disabl…

CDN,DNS,ADN,SCDN,DCDN,ECDN,PCDN,融合CDN的介绍

一、CDN是什么&#xff1f; CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节&#xff0c;使内容传输得更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之…

Windows系统搭建VisualSVN服务结合内网穿透实现公网访问

文章目录 前言1. VisualSVN安装与配置2. VisualSVN Server管理界面配置3. 安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4. 固定公网地址访问 前言 SVN 是 subversion 的缩写&#xff0c;是一个开放源代码的版本控制系统…

Ubuntu右上角不显示网络的图标解决办法

一.line5改为true sudo vim /etc/NetworkManager/NetworkManager.conf 二.重启网卡 sudo service network-manager stop sudo mv /var/lib/NetworkManager/NetworkManager.state /tmp sudo service network-manager start

超高频RFID模具精细化生产管理方案

近二十年来&#xff0c;我国的模具行业经历了快速发展的阶段&#xff0c;然而&#xff0c;模具行业作为一个传统、复杂且竞争激烈的行业&#xff0c;企业往往以订单为导向&#xff0c;每个订单都需要进行新产品的开发&#xff0c;从客户需求分析、结构确定、报价、设计、物料准…

大数据-玩转数据-Flink 海量数据实时去重

一、海量数据实时去重说明 借助redis的Set&#xff0c;需要频繁连接Redis&#xff0c;如果数据量过大, 对redis的内存也是一种压力&#xff1b;使用Flink的MapState&#xff0c;如果数据量过大, 状态后端最好选择 RocksDBStateBackend&#xff1b; 使用布隆过滤器&#xff0c;…

企业门户的必备选择,WorkPlus的定制化解决方案

在当今数字化时代&#xff0c;企业门户成为了企业内外沟通与协作的重要基础设施。WorkPlus作为领先的品牌&#xff0c;为企业提供了一站式的企业门户解决方案&#xff0c;旨在提升企业形象、改善内外部沟通与协作效率。本文将深入探讨WorkPlus如何通过定制化的设计&#xff0c;…

使用运放产生各种波形

目录复制 文章目录 RC正弦振荡电路文氏电桥振荡电路移项式正弦波振荡电路 集成函数发生器运算放大器驱动电容性负载峰值检波多通道运放未使用的运放接法 RC正弦振荡电路 文氏电桥振荡电路 这个振荡器起振条件RF > 2R1,起振后又希望RF 2R1产生矛盾怎么办&#xff1f; 将RF换…

Zama的fhEVM:基于全同态加密实现的隐私智能合约

1. 引言 Zama的fhEVM定位为&#xff1a; 基于全同态加密实现的隐私智能合约 解决方案 开源代码见&#xff1a; https://github.com/zama-ai/fhevm&#xff08;TypeScript Solidity&#xff09; Zama的fhEVM协议中主要包含&#xff1a; https://github.com/zama-ai/tfhe-…

东土科技与诺贝尔物理学奖2006年度得主斯穆特签约,加快布局工业AI

近日&#xff0c;诺贝尔物理学奖2006年度得主乔治.斯穆特教授与东土科技正式签约&#xff0c;成为东土科技工业人工智能顾问。 乔治斯穆特&#xff08;George Fitzgerald Smoot&#xff09;教授也曾获得爱因斯坦奖&#xff0c;在宇宙学、大数据、生物医学诊断仪器以及人工智能…

Leetcode hot 100之前缀和、差分数组、位运算

目录 差分数组-区间增减 和为K的子数组&#xff1a;前缀和 哈希表优化 除自身以外数组的乘积&#xff1a;前后缀区间 位运算 异或&#xff1a;同为0&#xff0c;不同为1 136. 只出现一次的数字&#xff1a;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现2次…

40V汽车级P沟道MOSFET SQ4401EY-T1_GE3 工作原理、特性参数、封装形式—节省PCB空间,更可靠

AEC-Q101车规认证是一种基于失效机制的分立半导体应用测试认证规范。它是为了确保在汽车领域使用的分立半导体器件能够在严苛的环境条件下正常运行和长期可靠性而制定的。AEC-Q101认证包括一系列的失效机制和应力测试&#xff0c;以验证器件在高温、湿度、振动等恶劣条件下的可…

面试经典 150 题 4 —(数组 / 字符串)— 80. 删除有序数组中的重复项 II

80. 删除有序数组中的重复项 II 方法一 class Solution { public:int removeDuplicates(vector<int>& nums) {int len 0;for(auto num : nums)if(len < 2 || nums[len-2] ! num)nums[len] num;return len;} };方法二 class Solution { public:int removeDupli…

【多线程进阶】线程安全的集合类

文章目录 前言1. 多线程环境使用 ArrayList2. 多线程环境使用队列3. 多线程环境使用哈希表3.1 HashTable3.2 ConcurrentHashMap 总结 前言 本文主要讲解 Java 线程安全的集合类, 在之前学习过的集合类中, 只有 Vector, Stack, HashTable, 是线程安全的, 因为在他们的关键方法中…

使用DNS查询Web服务器IP地址

浏览器并不具备访问网络的功能&#xff0c;其最终是通过操作系统实现的&#xff0c;委托操作系统访问服务器时提供的并不是浏览器里面输入的域名而是ip地址&#xff0c;因此第一步需要将域名转换为对应的ip地址 域名&#xff1a;www.baidu.com ip地址是一串数字 tcp/ip的网络结…

百面机器学习书刊纠错

百面机器学习书刊纠错 P243 LSTM内部结构图 2023-10-7 输入门的输出 和 candidate的输出 进行按元素乘积之后 要和 遗忘门*上一层的cell state之积进行相加。

开发者指南:如何集成一对一直播美颜SDK到你的应用中

本文将为开发者们提供一个详细的指南&#xff0c;教你如何将一对一直播美颜SDK集成到你的应用中&#xff0c;以提供更具吸引力的直播体验。 -为什么选择一对一直播美颜SDK&#xff1f; 在开始之前&#xff0c;让我们先明确一下为什么选择一对一直播美颜SDK是一个明智的决定。…

ueditor

下载文件 文档 UEditor入门部署 入门部署和体验 1.1 下载编辑器 到官网下载 UEditor 最新版&#xff1a;http://ueditor.baidu.com/website/download.html#ueditor 1.2 创建demo文件 解压下载的包&#xff0c;在解压后的目录创建 demo.html 文件&#xff0c;填入下面的…

Linux 安全 - LSM机制

文章目录 前言一、LSM起源二、LSM简介2.1 MAC2.2 LSM特征 三、Major and Minor LSMs3.1 Major LSMs3.2 Minor LSMs3.3 BPF LSM 四、LSM 框架五、LSM Capabilities Module六、LSM hooks 说明参考资料 前言 在这两篇文章中介绍了 Linux 安全机制 Credentials &#xff1a; Linu…