在线性时间内排序
通过比较确定两个元素之间相对位置的比较排序算法的时间复杂性下界为Ο(nlogn),然而当排序序列满足某种特定条件时,我们可以突破这个时间下界,在线性时间内就可以完成排序。
一、 计数排序
计数排序是一个非基于比较的线性时间排序算法。它对输入的数据有附加的限制条件:
1. 输入的线性表的元素属于有限偏序集 S;
2. 设输入的线性表的长度为n,|S|=k(表示集合S中元素的总数目为k),则k=O(n)。
在这两个条件下,计数排序的复杂性为O(n)。
基本思想:
是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数。一旦确定了这一信息,就可以将x直接存放到最终的输出序列的正确位置上。
例如,如果输入序列中只有9个元素的值小于x 的值,则x可以直接存放在输出序列的第10个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些相同的元素放在输出序列的同一个位置上,因此还需对上述方案作适当的修改。
排序过程:
假设输入的序列L的长度为n,L={L0 , L1 , … , Ln-1};线性表的元素属于有限偏序集S,|S|=k且k=O(n),S={S0 , S1 , … Sk-1};则计数排序算法可以描述如下:
1. 扫描整个集合S,对每一个Si∈S,找到在序列L中小于等于Si的元素的个数C(Si);
2. 扫描整个序列L,对L中的每一个元素Li,将Li放在输出线性表的第C(Li)个位置上,并将C(Li)减1。
在实现计数排序的过程中,我们需要两个辅助数组:B[0..n-1]存放排序结果,C[0..k-1]作为临时数组。下图说明了计数排序的过程。
所示的计数排序过程中可以看到,整个排序过程只需要对数组A进行两次扫描即可完成整个排序过程。首先,在从左到右的扫描数组A 的过程中,对S 中所有元素(即{0, 1, 2, 3, 4, 5})出现的次数进行计数,将统计结果存放在临时数组C 中,C 中每个分量C[i]的取值即为i 出现的次数。然后,使用第一步的统计结果,计算各个元素的输出位置。最后,从右到左扫描A,对其中每个元素根据C 中所指位置输出,并将对应输出位置大小减1。
效率:
通过以上对计数排序过程的分析,我们知道计数排序的时间复杂度T(n) = Θ(n + k),由于k=O(n),因此,T(n)= Θ(n)。并且计数排序是一种稳定的排序方法。
二、 基数排序
基数排序是一种“低位优先”的排序方法,它的基本思想是通过反复的对子关键字排序来完成排序。假设元素r[i]的关键字为keyi,keyi是由d位十进制数组成,即keyi=ki1 ki2… kid,
则每一位可以视为一个子关键字,其中ki1是最高位,kid是最低位,每一位的值都在0到9的范围内,此时基数rd=10。如果ki1是由d个英文字母组成,即keyi=ki1 ki2… kid,其中‘a’≤ kij≤‘z’(1≤ j≤d),则基数rd =26。
排序过程:
排序时先按最低位的值对元素进行初步排序,在此基础上再按次低位的值进行进一步排序。依次类推,由低位到高位,每一趟都是在前一趟的基础上,根据关键字的某一位对所有元素进行排序,直到最高位,这样就完成了计数排序的全过程。
例子:
下面我们先通过一个例子来说明基数排序的基本过程。假设对7个元素进行排序,每个元素的关键字是1000 以下的正整数。在此,每个关键字由三位子关键字构成k1k2k3,k1代表关键字的百位,k2代表关键字的十位,k3代表关键字的个位,基数rd = 10。排序的过程如图所示。
需要注意的是,在基数排序的过程中,当针对每一个子关键字进行排序时,需要使用稳定的排序方法。当基数rd不太大时,对于每个子关键字的排序而言,计数排序是一种很好的选择,因为其他稳定的排序方法时间复杂度太高。
效率:
从算法的执行过程中可以看出,对n个具有d位子关键字的元素进行排序,每一位子关键字的排序采用计数排序时,需要Θ(n+rd)的时间;排序一共进行d 趟,因此基数排序的时间复杂度T(n)=Θ(d(n+rd))。当d为常数,且rd太大,即rd=O(n)时,基数排序可以在线性时间内完成。