> 🍃 本系列包括常见的各种排序算法,如果感兴趣,欢迎订阅🚩
> 🎊个人主页:[小编的个人主页])小编的个人主页
> 🎀 🎉欢迎大家点赞👍收藏⭐文章
> ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
🐼前言
🌟在上一节我们实现了归并排序,感受到了归并排序的魅力,如果感兴趣的小伙伴,可以阅读我的上一篇文章:>归并排序 这节小编主要分享计数排序和快速排序的非递归版本。
🐼计数排序
🔍计数排序是一种非比较排序,而在我们上述实现的所有排序中,都是比较排序。
其思想是,统计相同元素出现的次数;
根据 统计的结果将序列回收到原来的序列中 。
☀️动图解析:
👀 从上述动图,我们可以看出,需要创建一个计数的数组,那么这个数组需要开辟多大的空间呢?
从上面的案例看到,如果最大值是9,就开辟10个(最大值+1个)空间,因为下标是从0开始的。那如果是{100,101,102,103,104,105}.那需要开辟105+1个空间吗?显示,这会造成空间浪费。如图:
❄️如果我们能求出这组序列的最大值和最小值。那么就可以根据序列的范围(range = max-min+1)向操作系统申请空间,并将count数组中所有元素置为0,刚开始还没有计数。这样用于计数的数组就创建好了。
❄️这样,将原数组所有元素都映射到计数数组中,映射规则是:当前值-这组序列中的最小值。比如{100,101,102,103,104,105}序列。101映射到计数数组下标就是(101-100 = 1),所以在count数组下标为1的位置计数加1。以此类推,将原序列所有元素都映射到计数数组中,正如我们动图所演示。
👀那么想得到排序好的序列该咋办呢?
我们知道,count数组中每个位置下标所对应的值,就是映射过来的该元素对应出现的次数。
那么我们还原回去,就需要将count每个位置映射回去(原数组元素=计数数组下标+min),而我们又知道,count数组的值就是原数组出现次数,那计数几次,就映射几次过去。最后,不要忘记释放申请的count数组。
具体代码:
#include<string.h>
//计数排序(非比较排序)
void CountSort(int* arr, int n)
{int min = arr[0], max = arr[0];//找最大最小值for (int i = 1; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min +1;//创建count数组,并初始化为0//写法1//int* count = (int*)calloc(sizeof(int)*range,sizeof(int));//写法2int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");exit(-1);}memset(count, 0, sizeof(int) * range);//计数for (int i = 0; i < n; i++){count[arr[i] - min]++;}//还原原数组int index = 0;for (int i = 0; i < range; i++){while (count[i]--){arr[index++] = i + min;}}free(count);count = NULL;
}
🐼快速排序(非递归)
🔍在之前,我们实现了快速排序的三种实现方式,今天来完成快速排序的非递归版本。
在快速排序时,需要不断地根据基准值划分左右区间,就像二叉树一样。非递归的快速排序需要用栈来模拟划不断分左右区间这个过程,就像模拟二叉树的实现方式。
如图:
🍃我们用栈来模拟这个过程,先让原序列右下标和左下标入栈(先让右入栈,因为栈先入后出),接着,只要栈不为空,就一次取两个元素,即左下标和右下标。再通过前面找基准值的三种方式,任意一种,找到keyi,此时划分左序列为[left,key-1],右序列[keyi+1,right],只要keyi + 1 < right,keyi-1>left,将右序列先入栈,再将左序列入栈。即入栈顺序:right->keyi+1->keyi-1->left.
具体代码
// 快速排序 非递归实现
void QuickSortNonR(int* arr, int left, int right)
{ST st;StackInit(&st);StackPush(&st,right);StackPush(&st, left);while (!StackEmpty(&st)){//取栈顶两个元素int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int prev = begin, cur = prev + 1;int keyi = begin;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}cur++;}Swap(&arr[prev], &arr[keyi]);//基准值keyi//左序列begin prev-1//右序列prev+1 endif (prev + 1 < end){StackPush(&st, end);StackPush(&st, prev + 1);}if (begin < prev - 1){StackPush(&st, prev - 1);StackPush(&st, begin);}}STDestory(&st);
}
🐼排序算法时间复杂度和稳定性
🔍稳定性:排序的稳定性是指在排序过程中,如果存在多个具有相同关键字的记录,这些记录在排序前后的相对顺序保持不变。这意味着在排序操作中,如果两个元素相同,它们在最终结果中的位置应与在原始数据集中的位置相同。
我们总结一下各种排序的性能对比:
感谢你看到这里,如果觉得本篇文章对你有帮助,点个赞👍 吧,你的点赞就是我更新的最大动力。⛅️🌈 ☀️
这车好有感觉~