一、排序的下界
判定树模型:任何基于比较的排序可以用一棵二叉树表示,其叶子节点代表排序结果。
定理:在最坏情况下,任何比较排序算法都需要做Ὼ(nlogn)次比较。
二、计数排序
基本思想:统计小于或等于A[i]的元素数目,将A[i]置入相应的位置,即 A[i]→B[小于或等于A[i]的元素数目]
问题:n个互不相同的整数A[1..n],1≤A[i]≤n i=1~n
算法:
SpecialCountingSort(A,B){
//B[1..n]为排序结果
for i←1 to n do
B[A[i]]←A[i]; //如A[i]=5,就放到B[5]中
}
时间:O(n) ,无比较
问题:n个可以相同的整数A[1..n],1≤A[i]≤k,i=1~n,这里k是A[i]的取值范围,不一定为n。
步骤:A[1..n]→计数器C[1..k]→B[1..n]
Step1(值相同元素的计数):将A中的值为i的元素个数记入C[i]中;
Step2(累计计数):对C[1..k]进行修改,使得C[i]的值表示为≤i的元素个数;
Step3(放置):将A[j]依据C[A[j]],放入正确位置B[C[A[j]]]上,并修改C[A[j]] ← C[A[j]]-1;
CountingSort(A,B,k){
//B[1..n]为排序结果,C[1..k]为计数数组
for i←1 to k do C[i]←0;
for j←1 to length[A] do //扫描A,值相同元素计数
C[A[j]]++;
for i←2 to k do //C[i]修改,累计计数
C[i]←C[i]+C[i-1];
for j←length[A]downto 1 do //保证排序算法是稳定的
B[C[A[j]]]←A[j];
C[A[j]]--;
}
时间:T(n,k)=θ(n+k)=θ(n) ,如果k=θ(n)时
计数排序的稳定性很重要因为它经常会被用作基数排序算法的一个子过程。
三、基数排序
是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数
它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
假定A[1..n]是非负整数,用k进制表示为不超过d位数。
算法:
RadixSort(A,d){
for i←1 to d do
使用稳定的排序算法对A的第i位排序;//如计数排序
}
时间:T(n)=θ(d(n+k)) //k为基,d为位数
=θ(n) //如果k=θ(n)且d是常数
基数排序的时间复杂度是O(d•n),其中n是排序元素个数,d是数字位数。注意这不是说这个时间复杂度一定优于O(n·log(n)),因为d的大小一般会受到 n 的影响。以排序n个不同整数来举例,假定这些整数以K为底,这样每位数都有K个不同的数字,d就一定不小于logK(n)。由于有K个不同的数字,所以就需要K个不同的桶,在每一轮比较的时候都需要平均n·log2(K) 次比较来把整数放到合适的桶中去,所以就有:
· d大于或等于 logK(n)
· 每一轮(平均)需要 n·log2(K)次比较
所以,基数排序的平均时间T就是:
T ≥ logK(n)·n·log2(K) = log2(n)·logK(2)·n·log2(K)= log2(n)·n·logK(2)·log2(K) = n·log2(n)
所以和比较排序相似,基数排序需要的比较次数:T ≥ n·log2(n)。故其时间复杂度为 Ω(n·log2(n)) = Ω(n·log n) 因此,不是线性时间排序。
算法何时为线性时间?
Idea:只要使d变为常数,k变大到与n同阶
Howto do:
设n个整数的取值范围是0~nc,c是整常数,c≥1
选基k=n,则nc的位数为lognnc=c=d
∴ d=c,k=n
∴ T(n)=θ(n)
四、桶排序
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到 O(nlogn)下限的影响。
假定:输入是均匀分布在[0,1)上的实数。
基本思想:
1.[0,1)划分为[0,1/n),[1/n,2/n),…,[k/n,(k+1)/n),…,[(n-1)/n,1)n个大小相等的子区间,每个子区间看作一个桶;
2.将n个元素分配到桶中;
3.对每个桶里的元素进行排序,依次连接桶;
算法:
输入:0≤A[1..n]<1
辅助数组:B[0..n-1]是一个指针数组,指向每个桶(链表)
关键字映射:由于0≤A[i]<1,必须将A[i]映射到0,1,…,n-1上
∵[0,1)→[0,n) //通过函数nA[i]
即k≤nA[i]<k+1 //存在k
∴桶号k=(nA[i]的下限) //映射函数
算法描述:
BucketSort(A){
n←length[A];
for i←1 to n do //扫描A
将A[i]插入到链表B[nA[i]的下限]中;
for i←0 to n-1 do
用插入排序将B[i]排序;
将B[0],B[1],…, B[n-1]连接起来;
}
注:∵n个数是均匀分布在[0,1)中,所以每个桶中大约只有一个数,时间为O(1)