[Alg]排序算法之分布排序

分布排序,又称桶排序或基数排序,是一种非比较型整数排序算法。文章详细介绍了如何通过基数排序算法对扑克牌进行排序,并探讨了基数排序与基数交换排序的区别。文中提供了一种基于链表的基数排序算法(算法H),并解释了其工作原理。此外,还讨论了在计算机补码表示下基数排序的特殊考虑。

[Alg]排序算法之分布排序

作者:屎壳郎

日期:Aug 2021

版次:初版


简介: 分布排序是与归并排序截然相反的处理思路,归并排序是逐步融合归并,而分布排序是分组然后合并,再分组再合并,所以分布排序又称为“桶排序”、“基数排序”或“数排 序”。它基于键值数字特性字分类,从而避免了比较操作。

1、引言

假设要对52张扑克牌排序,定义牌面键值顺序:
A<2<3<4<5<6<7<8<9<10<J<Q<KA<2<3<4<5<6<7<8<9<10<J<Q<KA<2<3<4<5<6<7<8<9<10<J<Q<K
定义花色键值顺序:
♣<♢<♡<♠\clubsuit<\diamondsuit<\heartsuit<\spadesuit<<<
两张牌确定先后顺序的条件为(i)它的花色小于另一张,或者(ii)两个花色相同,但牌面值小于另一张。通常的排序方法类似于基数交换排序(见《[Alg]排序算法之交换排序》,先根据花色分成四堆,然后再根据牌面值调整每一堆中牌的顺序。

还有一种更快的方法处理这类排序!首先根据牌面值分成13堆,先对这13堆排好序,即A<2<⋯<KA<2<\cdots<KA<2<<K;然后再根据花色分为4堆。然后合并到一起就得到排好序的结果。这个方法乍一看不显然。在第二步按花色分类时,如果两张牌进入不同的堆,说明花色不一样,会根据花色排序;如果两张牌具有一样的花色,那在第一步分类时已经排好序。同样的思想也可以应用到数字或字母类型的任意字典序的排序上。

NNN个记录,分成MMM堆,每个堆的容量要保证能容纳NNN个记录,这就需要M(N+1)M(N+1)M(N+1)的空间,这就是大部分人拒绝这个算法的原因。

2、基数排序

下面介绍一种基于键值的基数,采用链表法的排序算法,姑且称之为基数排序。下面举一个以10进制为基数排序的例子。
503  087  512  061  908  170  897  275  653  426  154  509  612  677  765  703503\;087\;512\;061\;908\;170\;897\;275\;653\;426\;154\;509\;612\;677\;765\;703503087512061908170897275653426154509612677765703

第一趟遍历,以个位数为基准分堆,结果如下:
在这里插入图片描述

完成后重新串连起来,见图中红色线(算法H):
在这里插入图片描述

第二趟遍历,以十位数为基准分堆,结果如下:
在这里插入图片描述

第三趟遍历,以百位数为基准分堆,结果如下:

在这里插入图片描述

总结算法如下:

算法R:(基数排序)

设记录R1,R2,…,RNR_1,R_2,\ldots,R_NR1,R2,,RN,包含链接地址。其对应的键值为:
(a1,a2,…,ap),0≤ai<M.(a_1,a_2,\ldots,a_p),\qquad 0\leq a_i<M.(a1,a2,,ap),0ai<M.
MMM为基数,其键值可表示为:
a1Mp−1+a2Mp−2+⋯+ap−1M+apa_1M^{p-1}+a_2M^{p-2}+\cdots+a_{p-1}M+a_pa1Mp1+a2Mp2++ap1M+ap

  • R1.[循环k] 在开始阶段,置P←LOC(RN)P\gets LOC(R_N)PLOC(RN),把指针指向最后一项。然后k=1,2,…,pk=1,2,\ldots,pk=1,2,,p,执行R2至R6,即完成一趟遍历。
  • R2.[清空堆] 置TOP[i]←LOC(BOTM[i])TOP[i]\gets LOC(BOTM[i])TOP[i]LOC(BOTM[i])BOTM[i]←ΛBOTM[i]\gets \LambdaBOTM[i]Λ for 0≤i<M0\leq i<M0i<M
  • R3.[解析键值kkk位] 置i←ap+1−ki\gets a_{p+1-k}iap+1k,即取键值kkk有效位。
  • R4.[调整链接] 置LINK(TOP[i])←PLINK(TOP[i])\gets PLINK(TOP[i])P,然后置TOP[i]←PTOP[i]\gets PTOP[i]P
  • R5.[移至下一个] 如果k=1k=1k=1(第一趟)并且P=LOC(Rj)P=LOC(R_j)P=LOC(Rj)j≠1j\neq1j=1,置P←LOC(Rj−1P\gets LOC(R_{j-1}PLOC(Rj1并返回R3。如果k>1k>1k>1,置P←LINK(p)P\gets LINK(p)PLINK(p)P≠ΛP\neq\LambdaP=Λ,返回R3。
  • R6.[执行串连程序] 置P←BOTM[0]P\gets BOTM[0]PBOTM[0],作为链表头,重新串连链表。

算法H:(串连链表)

  • H1.[初始化] 置i←0i\gets0i0
  • H2.[指向堆的顶端] 置P←TOP[i]P\gets TOP[i]PTOP[i]
  • H3.[下一堆] i←i+1i\gets i+1ii+1。如果i=Mi=Mi=M,置LINK(P)←ΛLINK(P)\gets\LambdaLINK(P)Λ并结束。
  • H4.[是空堆吗?] 如果BOTM[i]=ΛBOTM[i]=\LambdaBOTM[i]=Λ,返回H3。
  • H5.[把堆链起来] 置LINK(P)←BOTM[i]LINK(P)\gets BOTM[i]LINK(P)BOTM[i]。返回H2。

在《[Alg]排序算法之计数排序》中,讲了一个非常天才的分布统计排序,可以结合上述的基数排序使用,但前提是基数排序必须从最高有效位向低有效位分类。因为分布统计排序不是一个稳定的排序算法,而基数排序后续的分类基于前面排序的结果的,分布排序的不稳定性会破坏这个前提。但从最高有效位开始,就可以消除这种隐患,从最高位开始还有另一个好处,就是我们不用完全使用基数排序,可以只派前ppp位,剩余的可以采取其它排序算法来处理,以提高效率。

下面谈谈基数排序和基数交换排序的区别:

  • 1、基数交换排序是从最高有效位开始的,基数排序是从低位向高位(当然从高为向低位也可以);
  • 2、两种排序算法都不需要比较键值;
  • 3、基数交换排序是分成两组,而基数排序分组是可变的(人为选定,可以2,4,8,16都行);
  • 4、基数排序的运行时间是固定的O(Nlg⁡K)O(N\lg K)O(NlgK),而基数交换排序依赖于基数的分布情况而不依赖于键值KKK的大小O(Nlg⁡N)O(N\lg N)O(NlgN)
  • 5、基数交换排序不需要额外的链接。

还有一种情况要特别注意,就是计算机采取补码时,如果键值不是unsigned int类型,在最后处理时会有所不同。以M=256M=256M=256为例,最后串起各堆的顺序应该为:
(10000000)2,(10000001)2,…,(11111111)2,(00000000)2,(00000001)2,…,(01111111)2(10000000)_2,(10000001)_2,\ldots,(11111111)_2,(00000000)_2,(00000001)_2,\ldots,(01111111)_2(10000000)2,(10000001)2,,(11111111)2,(00000000)2,(00000001)2,,(01111111)2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值