常用的非比较排序算法:计数排序,基数排序,桶排序的详细资料概述

这篇文章中我们来探讨一下常用的非比较排序算法:计数排序,基数排序,桶排序。在一定条件下,它们的时间复杂度可以达到o(n)。
这里我们用到的唯一数据结构就是数组,当然我们也可以利用链表来实现下述算法。
计数排序(counting sort)
计数排序用到一个额外的计数数组c,根据数组c来将原数组a中的元素排到正确的位置。
通俗地理解,例如有10个年龄不同的人,假如统计出有8个人的年龄不比小明大(即小于等于小明的年龄,这里也包括了小明),那么小明的年龄就排在第8位,通过这种思想可以确定每个人的位置,也就排好了序。当然,年龄一样时需要特殊处理(保证稳定性):通过反向填充目标数组,填充完毕后将对应的数字统计递减,可以确保计数排序的稳定性。
计数排序的步骤如下:
统计数组a中每个值a[i]出现的次数,存入c[a[i]]
从前向后,使数组c中的每个值等于其与前一项相加,这样数组c[a[i]]就变成了代表数组a中小于等于a[i]的元素个数
反向填充目标数组b:将数组元素a[i]放在数组b的第c[a[i]]个位置(下标为c[a[i]] – 1),每放一个元素就将c[a[i]]递减
计数排序的实现代码如下:
#include
usingnamespacestd;
// 分类 ------------ 内部非比较排序
// 数据结构 --------- 数组
// 最差时间复杂度 ---- o(n + k)
// 最优时间复杂度 ---- o(n + k)
// 平均时间复杂度 ---- o(n + k)
// 所需辅助空间 ------ o(n + k)
// 稳定性 ----------- 稳定
constintk=100;// 基数为100,排序[0,99]内的整数
intc[k];// 计数数组
voidcountingsort(inta[],intn)
{
for(inti=0;i {
c[i]=0;
}
for(inti=0;i {
c[a[i]]++;
}
for(inti=1;i=0;i--)// 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
{
b[--c[a[i]]]=a[i];// 把每个元素a[i]放到它在输出数组b中的正确位置上
// 当再遇到重复元素时会被放在当前元素的前一个位置上保证计数排序的稳定性
}
for(inti=0;i {
a[i]=b[i];
}
free(b);// 释放临时空间
}
intmain()
{
inta[]={15,22,19,46,27,73,1,19,8};// 针对计数排序设计的输入,每一个元素都在[0,100]上且有重复元素
intn=sizeof(a)/sizeof(int);
countingsort(a,n);
printf(计数排序结果:);
for(inti=0;i {
printf(%d ,a[i]);
}
printf(\n);
return0;
}
下图给出了对{ 4, 1, 3, 4, 3 }进行计数排序的简单演示过程
计数排序的时间复杂度和空间复杂度与数组a的数据范围(a中元素的最大值与最小值的差加上1)有关,因此对于数据范围很大的数组,计数排序需要大量时间和内存。
例如:对0到99之间的数字进行排序,计数排序是最好的算法,然而计数排序并不适合按字母顺序排序人名,将计数排序用在基数排序算法中,能够更有效的排序数据范围很大的数组。
基数排序(radix sort)
基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机上的贡献。它是这样实现的:将所有待比较正整数统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始进行基数为10的计数排序,一直到最高位计数排序完后,数列就变成一个有序序列(利用了计数排序的稳定性)。
基数排序的实现代码如下:
#include
usingnamespacestd;
// 分类 ------------- 内部非比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- o(n * dn)
// 最优时间复杂度 ---- o(n * dn)
// 平均时间复杂度 ---- o(n * dn)
// 所需辅助空间 ------ o(n * dn)
// 稳定性 ----------- 稳定
constintdn=3;// 待排序的元素为三位数及以下
constintk=10;// 基数为10,每一位的数字都是[0,9]内的整数
intc[k];
intgetdigit(intx,intd)// 获得元素x的第d位数字
{
intradix[]={1,1,10,100};// 最大为三位数,所以这里只要到百位就满足了
return(x/radix[d])%10;
}
voidcountingsort(inta[],intn,intd)// 依据元素的第d位数字,对a数组进行计数排序
{
for(inti=0;i {
c[i]=0;
}
for(inti=0;i {
c[getdigit(a[i],d)]++;
}
for(inti=1;i=0;i--)
{
intdight=getdigit(a[i],d);// 元素a[i]当前位数字为dight
b[--c[dight]]=a[i];// 根据当前位数字,把每个元素a[i]放到它在输出数组b中的正确位置上
// 当再遇到当前位数字同为dight的元素时,会将其放在当前元素的前一个位置上保证计数排序的稳定性
}
for(inti=0;i {
a[i]=b[i];
}
free(b);
}
voidlsdradixsort(inta[],intn)// 最低位优先基数排序
{
for(intd=1;d<=dn;d++)// 从低位到高位
countingsort(a,n,d);// 依据第d位数字对a进行计数排序
}
intmain()
{
inta[]={20,90,64,289,998,365,852,123,789,456};// 针对基数排序设计的输入
intn=sizeof(a)/sizeof(int);
lsdradixsort(a,n);
printf(基数排序结果:);
for(inti=0;i {
printf(%d ,a[i]);
}
printf(\n);
return0;
}
下图给出了对{ 329, 457, 657, 839, 436, 720, 355 }进行基数排序的简单演示过程
基数排序的时间复杂度是o(n*dn),其中n是待排序元素个数,dn是数字位数。这个时间复杂度不一定优于o(n log n),dn的大小取决于数字位的选择(比如比特位数),和待排序数据所属数据类型的全集的大小;dn决定了进行多少轮处理,而n是每轮处理的操作数目。
如果考虑和比较排序进行对照,基数排序的形式复杂度虽然不一定更小,但由于不进行比较,因此其基本操作的代价较小,而且如果适当的选择基数,dn一般不大于log n,所以基数排序一般要快过基于比较的排序,比如快速排序。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序并不是只能用于整数排序。
桶排序(bucket sort)
桶排序也叫箱排序。工作的原理是将数组元素映射到有限数量个桶里,利用计数排序可以定位桶的边界,每个桶再各自进行桶内排序(使用其它排序算法或以递归方式继续使用桶排序)。
桶排序的实现代码如下:
#include
usingnamespacestd;
// 分类 ------------- 内部非比较排序
// 数据结构 --------- 数组
// 最差时间复杂度 ---- o(nlogn)或o(n^2),只有一个桶,取决于桶内排序方式
// 最优时间复杂度 ---- o(n),每个元素占一个桶
// 平均时间复杂度 ---- o(n),保证各个桶内元素个数均匀即可
// 所需辅助空间 ------ o(n + bn)
// 稳定性 ----------- 稳定
/* 本程序用数组模拟桶 */
constintbn=5;// 这里排序[0,49]的元素,使用5个桶就够了,也可以根据输入动态确定桶的数量
intc[bn];// 计数数组,存放桶的边界信息
voidinsertionsort(inta[],intleft,intright)
{
for(inti=left+1;i=left&&a[j]>get)
{
a[j+1]=a[j];
j--;
}
a[j+1]=get;
}
}
intmaptobucket(intx)
{
returnx/10;// 映射函数f(x),作用相当于快排中的partition,把大量数据分割成基本有序的数据块
}
voidcountingsort(inta[],intn)
{
for(inti=0;i {
c[i]=0;
}
for(inti=0;i {
c[maptobucket(a[i])]++;
}
for(inti=1;i=0;i--)// 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
{
intb=maptobucket(a[i]);// 元素a[i]位于b号桶
b[--c[b]]=a[i];// 把每个元素a[i]放到它在输出数组b中的正确位置上
// 桶的边界被更新:c[b]为b号桶第一个元素的位置
}
for(inti=0;i {
a[i]=b[i];
}
free(b);
}
voidbucketsort(inta[],intn)
{
countingsort(a,n);// 利用计数排序确定各个桶的边界(分桶)
for(inti=0;i {
intleft=c[i];// c[i]为i号桶第一个元素的位置
intright=(i==bn-1?n-1:c[i+1]-1);// c[i+1]-1为i号桶最后一个元素的位置
if(left insertionsort(a,left,right);
}
}
intmain()
{
inta[]={29,25,3,49,9,37,21,43};// 针对桶排序设计的输入
intn=sizeof(a)/sizeof(int);
bucketsort(a,n);
printf(桶排序结果:);
for(inti=0;i {
printf(%d ,a[i]);
}
printf(\n);
return0;
}
下图给出了对{ 29, 25, 3, 49, 9, 37, 21, 43 }进行桶排序的简单演示过程
桶排序不是比较排序,不受到o(nlogn)下限的影响,它是鸽巢排序的一种归纳结果,当所要排序的数组值分散均匀的时候,桶排序拥有线性的时间复杂度。

看好笔记本电脑触控商机 禾瑞亚明年迎出货高峰
SUV增速放缓已成趋势,黄金期真的仅剩三年了?
业界首创!5G-A算网一体游牧式基站赋能体育赛事
半导体封装及测试厂商蓝箭电子正式登陆创业板
承德科胜颗粒全自动包装机|胡辣汤料包装机|河北包装机
常用的非比较排序算法:计数排序,基数排序,桶排序的详细资料概述
武汉芯源半导体CW32F030系列MCU在电焊机的应用
ARM ATF入门-安全固件软件介绍和代码运行
德国汽车制造商戴姆勒和宝马正式宣布,双方将深化此前的合作
中国将稳居世界第二大半导体设备市场
和你一起一步步看懂排序算法的运行过程
《S参数及耦合干扰》专项训练开课啦!
迎接小米MIX2的到来?小米MIX现在3019就可买到 真正的全面屏
“保姆级”智能驾驶的照顾,谁不想拥有?
美新半导体:三合一MEMS芯片实现更高性价比
PCB设计中焊盘的种类以及设计标准解析
智能机器人正处在一个最好的时代
英飞凌助攻马自达最新增程序电动车接入富田电机七合一驱动系统
机器人的SLAM和导航系统技术分析
体验经济是 ToB 领域的下一个关注点