vlambda博客
学习文章列表

第七章 内排序(6)——基数排序及本章小结



基数排序



基数排序(Radix Sort)是一种借助于多关键字进行排序,也就是一种将单关键字按基数分成“多关键字”进行排序的方法。

多关键字排序

首先看一个例子:

扑克牌中有52张牌,可按花色和面值分成两个属性,设其大小关系为:

花色:梅花<方块<红心<黑心

面值:2<3<4<5<6<7<8<9<10<J<Q<K<A

若对扑克牌按花色、面值进行升序排序,则得到如下序列:

梅花2,3,…,A,方块2,3,…,A,红心2,3,…,A,黑心2,3,…,A

即两张牌,若花色不同,不论面值怎样,花色低的那张牌小于花色高的,只有在同花色情况下,大小关系才由面值的大小确定,这就是多关键字排序。为得到排序结果,下面讨论两种排序方法。

方法1:先对花色排序,将其分为4个组,即梅花组、方块组、红心组和黑心组;再对每个组分别按面值进行排序;最后,将4个组连接起来即可。

方法2:先按13个面值给出13个编号组(2号,3号,…,A号),将牌按面值依次放入对应的编号组,分成13堆;再按花色给出4个编号组(梅花、方块、红心、黑心),将2号组中的牌取出分别放入对应花色组,再将3号组中的牌取出分别放入对应花色组·····这样,4个花色组中均按面值有序;最后,将4个花色组依次连接起来即可。

假设n个记录的排列表中的每个记录包含d个关键字{k1,k2,…,kd},排列表有序是指对于排列表中任意两个记录r[i]和r[j](1≤i≤j≤n),都满足有序关系:(ki1,ki2,…,kid)<(kj1,kj2,…,kjd)

其中,k1称为最主位关键字,kd称为最次位关键字。

多关键字排序按照从最主位关键字到最次位关键字,或从最次位关键字到最主位关键字的顺序逐次排列,可分为两种方法:

(1)最主位优先(Most Significant Digit First)法,简称MSD法:先按k1排序分组,同一组中记录,若关键字k1相等,再对各组按k2排序分成子组;之后,对后面的关键字继续这样的排序分组,直到按最次位关键字kd对各子表排序后,再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法1即是MSD法。

(2)最次位优先(Least Significant Digit First)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法2即是LSD法。



链式基数排序



将关键字拆分为若干项,每项作为一个“关键字”,则对单关键字的排序可按多关键字排序方法进行。例如:关键字为4位的整数,可以每位对应一项,拆分成4项;又例如:关键字由5个字符组成的字符串,可以每个字符作为一个关键字。由于这样拆分后,每个关键字都在相同的范围内(数字是0~9,字符是‘a’~‘z’),称这样的关键字可能出现的符号个数为“基”,记作RADIX。上述取数字为关键字的“基”为10;取字符为关键字的“基”为26。基于这一特性,采用LSD法排序较为方便。

在基数排序中,常使用d表示关键字的位数,用rd表示关键字可取值的种数。例如,关键字为一个3位数,则d=3,每一位关键字为数字,rd=10。若关键字是长度为4的字符串,则d=4,每一位关键字为字母,rd=26。

基数排序的基本思想是将一个序列中的逻辑关键字看成是由d个关键字复合而成,并采用最低位优先方法对该序列进行多关键字排序,即从最低关键字开始,将整个序列中的元素“分配”到rd个队列中,再依次“收集”成一个新的序列,如此重复进行d次,即完成排序过程。

执行基数排序可采用链表的存储结构,用一个长度为n的单链表r存放待排序的几个记录,再使用两个长度为rd的一维数组f和e,分别存放rd个队列中指向队首结点和队尾结点的指针。其处理过程为:

(1)形成初始链表作为当前的处理序列。

(2)将最小的关键字值作为当前关键字,即i=d。

(3)执行第i趟分配和收集,即改变序列中各个记录的指针,使当前的处理序列按该关键字分成rd个子序列,链头和链尾分别由f[0..rd-1]和e[0..rd-1]指向,再将这rd个子序列头尾相连形成一个新的当前处理序列。

(4)将当前关键字向高位推进一位,即i=i-1;重复执行步骤(3),直至d位关键字都处理完毕。

例如:初始的记录关键字为(179,208,306,930,589,184,505,269,008,083),则对其进行基数排序的执行过程如下图所示。

图(a)表示由初始关键字序列所连成的链表。第1趟分配对最低位关键字(个位数)进行,改变结点指针值,将链表中的各个结点分配到10个链队列中去,每个队列中的结点关键字的个位数相等,如图(b)所示,其中f[i]和e[i]分别为第i个队列的首指针和尾指针,第1趟收集是改变所有非空队列的队尾结点的指针域,令其指向下一个非空队列的队首结点,重新将10个队列中的结点连成一条链,如图(c)所示,第2趟分配与收集及第3趟分配与收集分别是对十位数和百位数进行的,其过程和个位数相同,如图(d)~图(g)所示,至此排序完毕。

算法性能分析:

(1)时间复杂度。假设待排序列含有个记录,共d位关键字,每位关键字的取值范围为0~rd-1,则进行链式基数排序的时间复杂度为O(d(n+rd)),其中,一趟分配的时间复杂度为O(n),一趟收集的时间复杂度为O(rd),因此,共进行了d趟分配和收集。

(2)空间复杂度。需要2×rd个队列首、尾指针辅助空间以及用于链表的n个指针。

(3)算法稳定性。基数排序是一种稳定的排序算法。



小结



本章介绍了常见的内部排序方法,包括插入排序、交换排序、选择排序、归并排序和基数排序。下面先从各种内部排序方法的时间复杂度、空间复杂度和稳定性等方面进行比较和分析,然后给出根据实际问题选择合适排序方法的建议。

①时间复杂度

按平均时间性能来分,有3类排序方法:

时间复杂度为O(nlog2n)的方法有快速排序、堆排序和归并排序,其中以快速排序为最好;

时间复杂度为O(n²)的方法有直接插入排序、冒泡排序和直接选择排序,其中以直接插入排序为最好,特别是对那些对关键字近似有序的记录序列尤为如此;

时间复杂度为O(n)的排序方法是基数排序。

当待排记录序列按关键字顺序有序时,直接插入排序和冒泡排序能达到O(n)的时间复杂度;而对于快速排序而言,这是最坏的情况,此时的时间性能蜕化为O(n²),因此,应该尽量避免这种情况。

②空间复杂度

空间复杂度是指在排序过程中所需要的辅助空间的大小。所有的简单排序方法(包括直接插入排序、冒泡排序和直接选择排序)和堆排序的空间复杂度为O(1);快速排序单独属于一类,其空间复杂度为O(log2n);归并排序所需要辅助空间最多,其空间复杂度为O(n);链式基数排序需附设队列首、尾指针,其空间复杂度为O(rd)。

③排序算法的稳定性

从排序算法的稳定性上看,稳定的排序算法包括直接插入排序、冒泡排序、归并排序和树形选择排序、基数排序;不稳定的排序算法包括希尔排序、直接选择排序、快速排序和堆排序。

各种内部排序算法的性能比较如下表所示。

第七章 内排序(6)——基数排序及本章小结

因为不同的排序算法适用于不同的应用环境和要求,因此,在解决实际的排序问题时选择合适的排序方法要综合考虑多种因素,包括:待排序的记录数目、稳定性、时间复杂度和空间复杂度等。

下面给出几点选择排序算法的建议。

(1)若n较小(例如n<50),则可采用直接插入排序或直接选择排序。

(2)若记录序列初始状态基本有序(指正序),则应选用直接插入排序、冒泡排序。

(3)若n较大,则应采用时间复杂度为O(nlog2n)的排序算法:快速排序、堆排序或归并排序。

快速排序是目前基于比较的内部排序中被认为是最好的一种排序算法,当待排序的关键字是随机分布时,快速排序的平均时间最短;堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况,这两种排序都是不稳定的;当内存空间允许,且要求排序是稳定时,则可选用归并排序。

由于排序操作在计算机应用中所处的重要地位,希望读者能够深刻理解各种内部排序算法的基本思想和特点,熟悉内部排序的执行过程,记住各种排序算法的稳定性、空间复杂度和在最好、最坏情况下的时间复杂度,以便在实际应用中,根据实际问题的要求,选择合适的排序方法。


第七章 内排序(6)——基数排序及本章小结