前面介紹的演算法都有一個共同的性質:排序結果中,各元素的次序基於輸入時間的比較,我們把這類排序演算法稱為比較排序。
8.1比較排序演算法的時間下界
決策樹模型
比較排序的過程可以被抽象地視為決策樹。一棵決策樹是一棵滿二叉樹,表示某排序演算法作用於給定輸入所做的所有比較。排序演算法的執行對應於遍曆一條從樹的根到分葉節點的路徑。每個內結點對應一個比較ai&aj,左子樹決定著ai<=aj以後的比較,右子樹決定著ai>aj以後的比較。當到達一個分葉節點時,排序演算法就已確定。排序演算法能夠正確工作的的必要條件是,n個元素的n!種排列都要作為決策樹的一個分葉節點出現。設決策樹的高度為h,葉子數目為l,那麼有 2h>=l>=n!, 於是有 h>lgn! = Ω(nlgn)。 這說明比較排序的最壞時間複雜度為Ω(nlgn)。這也說明合并排序和堆排序的複雜度已經漸進最優了。
練習:
8.1-1 在比較排序的決策樹中,一個分葉節點最小可能的深度是多少?
分析: n-1。因為至少要比較n-1次。不知道有沒有更加理論化的證明?
8.1-3 證明:對於長度為n的n!種輸入中的至少一半而言,不存在具有線性時間的比較排序演算法。對n!的1/n部分而言又怎樣?1/2n部分呢?
分析:假設在決策樹種,m個分葉節點的深度為 h =O(n);那麼有2h > m,於是可知 h為Ω(lgm)。將m=n!/2代入,可知這與h=O(n)相矛盾。同樣,對於1/n*n!和1/2n*n!也一樣。
8.1-4 現有n個元素要排序,輸入序列為n/k個子序列,每個包含k個元素,每個子序列中的元素都小於後繼子序列中的元素,大於前驅子序列中的元素。這樣只要對個子序列中的k各元素進行排序就可以得到對整個輸入序列的排序結果,證明:這個排序問題中所需的比較次數有一個下界Ω(nlgk)。
分析: 每個k元素子序列的排列數目為k!,那麼整個序列在上述條件下的排列數目為(k!)n/k。按決策樹的分析方法,決策樹的深度為h>lg((k!)n/k) = n/k lg (k!)>n/k lg (k/2)k/2= n/2lgk/2。因此 h=Ω(nlgk)。
8.2計數排序
計數排序假設n個輸入元素的每一個都是介於0到k之間的整數,此處k為某個整數。當k=O(n)時,計數排序的已耗用時間為Θ(n)。
計數排序的思想就是對每一個輸入元素x,確定出小於x的元素個數。有了這一資訊,就可以把x直接放到最終輸出數組的為位置上了。
下面是計數排序的偽碼,假定輸入是數組A[1...n], 存放排序結果的B[1...n],以及提供計數臨時儲存的C[0...k]。
COUNTING-SORT(A,B,k)
1 for i <-- 0 to k
2 do C[i] <-- 0
3 for j <-- 1 to length[A]
4 do C[A[j]] <-- C[A[j]]+1
5 for i <-- 1 to k
6 do C[i] = C[i] + C[i-1]
7 for i <-- length[A] downto 1
8 do B[C[A[i]] = A[i]
9 C[A[i]] = C[A[i]] -1
計數排序的已耗用時間為Θ(n+k), 且計數排序是穩定的。計數排序之所以能夠突破前面所述的Ω(nlgn)極限,是因為它不是基於元素比較的,它是基於元素值的先驗知識的。計數排序雖然漸進複雜度上要優於比較排序,但它的常數因此明顯較大,而且不是就地排序。所以在實際的選擇上,還要考慮到輸入的特點,輸入的規模,記憶體限制等因素。
練習:
8.2-4請給出一個演算法,使之對給定的介於0到k之間的n個整數進行預先處理,並能在O(1)時間內回答出輸入的整數中有多少個落在[a...b]區間內。你給出的演算法的預先處理時間為Θ(n+k)。
分析:就是用計數排序中的預先處理方法,獲得數組C[0...k],使得C[i]為不大於i的元素的個數。這樣落入[a...b]區間內的元素個數有C[b]-C[a-1]。
8.3基數排序
假設長度為n的數組A中,每個元素都有d位元字,其中第一位是最低位,第d位是最高位,基數排序的演算法如下:
RADIX-SORT(A,d)
1 for i <-- 1 to d
2 do use a stable sort to sort array A on digit i
引理8.3: 給定n個d位元,每一個數位可以取k種可能的值,如果所用穩定排序需要Θ(n+k)時間,基數排序能以Θ(d(n+k))的時間完成。
證明:如果採用計數排序這種穩定排序,那麼每一遍處理需要時間Θ(n+k),一共需處理d編,於是基數排序的已耗用時間為Θ(d(n+k))。
在將關鍵字分成若干位方面,我們又一定的靈活性。
引理8.4:給定n個b位元和任何正整數r<=b,RADIX-SORT能在Θ((b/r)(n+2r))時間內正確地對這些數進行排序。
證明:對於一個r<=b,將每個關鍵字看成由d=b/r位組成的數,每一個數字都是(0~2r-1)之間的一個整數,這樣就可以採取計數排序了。總的已耗用時間為Θ((b/r)(n+2r))。
對於給定的n值和b值,如何選擇r值使得最小化運算式(b/r)(n+2r)。如果b< lgn,對於任何r<=b的值,都有(n+2r)=Θ(n),於是選擇r=b,使計數排序的時間為Θ((b/b)(n+2b)) = Θ(n)。 如果b>lgn,則選擇r=lgn,可以給出在某一常數因子內的最佳時間:當r=lgn使,演算法複雜度為Θ(bn/lgn),當r增大到lg以上時,分子2r增大比分母r快,於是已耗用時間複雜度為Ω(bn/lgn);反之當r減小到lgn以下的時候,b/r增大,而n+2r仍然是Θ(n)。
練習:
8.3-4 說明如何在O(n)時間內,對0~n2-1之間的n個數進行排序。
分析:依據引理8.4,取b=lg(n2) = 2lgn, r = lgn。基數排序的時間複雜度為Θ(2(n+n)) = Θ(n)。
8.4桶排序
計數排序假設輸入是由一個範圍內的整數構成,而桶排序假設輸入是由一個隨機過程產生,該過程均勻而獨立地分布在區間[0,1)上。
桶排序的思想就是把區間[0,1)劃分成n個相同大小的子區間,或稱桶。然後將n個數分布到各個桶中去。由於分布是均勻的,所以不會有很多數落在一個桶中的情況,為了得到結果,先對各桶中的元素進行排序,然後按次序把各桶中的元素列出來即可。
以下是桶排序的代碼,假設輸入的是一個包含n個元素的數組A,每個元素滿足A[i]<1。另外B[0...n-1]是一個存放桶的數組,並假設可以採用某種機制來維護這些表。
BUCKET-SORT(A)
1 n <-- length[A]
2 for i <-- 1 to n
3 do insert A[i] into list B[nA[i]]
4 for i <-- 0 to n-1
5 do sort list B[i] with insertion sort
6 concatenate the lists B[0],B[1],...,B[n-1] together in order
桶排序的期望時間分析
設ni為第3行後,各桶中的元素數量,於是演算法的已耗用時間可表示為 T(n) = Θ(n) + ∑i=0~n-1O(ni2)。
現分析某個桶元素的數量期望值。假設指標隨機變數 Xij = I{A[j]落在桶i中}。 P{Xij=1} = 1/n。
ni = ∑j=1~nXij
E[ni2] = E[∑j=1~nXij * ∑j=1~nXij] = E[∑j=1~nXij2+∑j=1~n∑k=1~n,k!=jXij Xik] = ∑j=1~nE[Xij2] + +∑j=1~n∑k=1~n,k!=jE[Xij Xik]
E[Xij2] = 1/n
E[XijXik] = 1/n*1/n = 1/n2
於是E[ni2] = 2-1/n。
可知E[T(n)] = Θ(n)。
即使輸入不滿足均勻分布,桶排序可以以線性時間運行,只要滿足各個桶尺寸的平方和與總的元素數量呈線性關係。
練習:
8.4-4. 在單位圓中有n個點,pi = (xi,yi),使得0<xi2+yi2<=1, i=1,2,...,n。假設所有的點式均勻分布的,亦即點落在落在圓的任意地區的機率與該地區的面積成正比。請設計一個Θ(n)期望時間的演算法,來依據點到原點之間的距離對n個點排序。
分析:假設圓的面積為S,那麼如果可以將分成每個面積S/n的n個地區,又由於我們的目標是按點掉圓心的距離排序,所以劃分地區的方式是圍繞圓心,一圈一圈地劃分,劃分的方法如下:選定一些列的距離d0,d1,...,dn,滿足d0=0, πd12=S/n, πd22=2*S/n,...,πdn2=S。 那麼離圓心距離為di-1到di之間的地區構成了桶i。
思考題: