這一篇主要是關於快速排序的隨機化版本和線性時間排序。前者即跟遞迴有關,又跟隨機演算法和概率分布有關,與後者合併作為一篇。主要記錄一些演算法的實現,和部分題目的思路。
快速排序效能的好壞和partition過程中陣列最後乙個值(主元)密切相關,事實上我們希望待排序陣列的任意乙個排列都是等可能出現,從而使主元的期望落在 $n/2$ 的位置。現實情況並非如此,待排序的陣列幾乎不可能是隨機的。隨機化的快速排序在partition過程中並不選取陣列中最後乙個值作為主元,而是隨機地選取乙個值分割,這樣就保證了主元的期望落在 $n/2$ 的位置。
int partitionrandom(int *x, int p, intr)void quicksortrandom(int *x, int p, int
r)}
思考題7-3「漂亮」的stooge-sort排序演算法
stooge-sort(a, i, j)if a[i]>a[j] then exchange a[i] and a[j]
if i+1>=j return
int k = (j-i+1)/3
stooge-sort(a, i, j-k)
stooge-sort(a, i+k, j)
stooge-sort(a, i, j-k)
證明該演算法可以對 $a[1...n]$ 正確排序:交換首尾項的工作,實際上只有在陣列規模為2的時候才有實際意義,而且陣列規模為2時確實能夠正確對其排序。演算法最後三次對自身的遞迴呼叫,保證了整個陣列都能夠完全排序了。我沒有作具體的證明,但是想象乙個全逆序的陣列(最極端的情況)執行這個演算法,就能很好地體會其中的含義了——為什麼對前 2/3 要呼叫兩次?這讓原本在後 1/3 段的,但排序在前 1/3 的元素能夠「翻」到前面去。為什麼是 2/3 這個數字?這樣重疊的部分就有足夠的空間容納最極端的元素,當全逆序的時候,第一次和第二次呼叫間,中間重疊的 1/3 全部是排序後 1/3的元素,第二次和第三次呼叫間,中間重疊的 1/3 部分全部是排序前 1/3 的元素。
給出該演算法的代價 $t(n)$ 。
$$t(n)=3t(2n/3)+\theta(1)=\theta(n^3})>\theta(n^)$$
這不是乙個好演算法。
思考題7-4尾遞迴。函式中對自己的最後一次遞迴呼叫,可以用迭代來實現。比如quicksort演算法可以這樣寫。
quicksorttail(a, p, r)while pint q =partition(a, p, r)
quicksorttail(a, p, q-1
) p = q+1
給出耗用堆疊深度 $\theta(n)$ 的情況。思路:遞迴過程耗用棧空間,迭代則不會,演算法中每次劃分陣列為兩部分,前面一半遞迴呼叫,後面一半迭代呼叫。如果每次前面一半都常數地大,後面一半都常數地小,就會使堆疊深度耗用為 $\theta(n)$ 。這樣的排列會產生上述情況(數字代表該元素在排序後的陣列中的索引):$n,n-2,n-4.....4,2,1,3,5,......n-3,n-1$
修改quicksorttail使得耗費堆疊深度至多為 $\theta(\lg n)$:很簡單,劃分後判斷一下,短的那部分拿來遞迴,長的那部分拿來迭代。
quicksorttail(a, p, r)while pint q =partition(a, p, r)
if(r-q > q-p)
quicksorttail(a, p, q-1
) p = q+1
else
quicksorttail(a, q+1
, r)
r = q-1
思考題7-5三數取中。在快速排序的隨機化版本中,一種更好的方法是「仔細地選擇」主元而不是隨機選擇。選擇的過程是:隨機選三個數,取中間的乙個數作為主元。
與一般實現相比,取到中值的概率增加了多少,給出 $n$ 趨向於極限時兩個概率比值。
一般的實現:$f(i)=pr\=1/n$
三數取中:
$$g(i)=pr'\=\frac^}=\frac$$
當 $n$ 較大時,二者的比值
$$\lim_ \frac=\frac/2n(n-1)(n-2)}=\frac$$
如果定義乙個「好的」劃分是:主元取值在 $n/3\leq i\leq 2n/3$,得到乙個「好的」劃分的概率增加了多少?
一般的實現,很顯然: $pr\=1/3$
三數取中:
$$pr'\=\lim_\int _^ pr'\di=\lim_\int _^\fracdi=\frac$$
增加了 $4/27$ 。
思考題7-6對區間的模糊排序。考慮對一組區間 $a[x_, y_],i=1,2...n$ 排序,只要滿足存在一組已排序的 $c_,i=1,2...n$ 使得 $c_\in [x_, y_]$,就認為這組區間是已模糊排序的。對端點 $x_$ 排序可以達到排序區間的目的。設計乙個更好的演算法對區間進行模糊排序,充分利用重疊區域。
思路:對端點進行快速排序,並且一邊排序一邊判斷:如果兩個區間有重疊區域,就認為他們相等,是同乙個區間(問題的規模縮小了1),並且二者都等於該區間的重疊區域。
計數排序用於具有可數的離散關鍵字的排序,通過計算某乙個關鍵字出現過的次數來統計每個關鍵字應當處在的位置。比如給0~99的1000個整數排序,對每乙個數字(一共100個)統計出現的次數(比如:76這個數字出現了12次),再對每個數字,統計比他小或相等的數字出現了多少次,如比76小或相等的數字出現了8000次。最後再還原出排序後的陣列。我的實現如下。
void countsort(int *x, int length, int k1, intk2)
for (int i=0; i)
for (int i=1; i1; i++)
for (int i=length-1; i>=0; i--)
delete a;
delete c;
}
排序十進位制的 $n$ 位整數,先將其對個位排序,再對十位排序……直到最高位。因為個位的取值只可能是 $0~9$ 所以對某一位的排序非常適合使用計數排序。這種方法也適合用來對字串進行排序。我的實現如下。
void countsortradix(int *x, int length, int k1, int k2, intradix)
for (int i=0; i<=k2-k1; i++)
for (int i=0; i)
for (int i=1; i1; i++)
for (int i=length-1; i>=0; i--)
delete a;
a =null;
delete c;
c =null;
}void radixsort(int* x, int length, int
radixcount)
}
思考題8-4水壺問題。共有 $n$ 個紅色水壺和 $n$ 個藍色水壺,兩兩是一對,容積一樣。紅色水壺內部體積各不一樣,藍色水壺也是。只能比較紅色和藍色水壺的體積,而不能比較紅色和紅色、藍色和藍色水壺體積。提出 $o(n\lg n)$ 的演算法為所有水壺配對。
思路:隨機挑選乙隻紅色水壺與所有的藍色水壺比較,可以找出容積一樣的藍色水壺,並將剩餘的藍色水壺分為兩類。再隨機挑選乙個紅色水壺,與剛才配對成功地藍色水壺比較,再從兩類藍色水壺中選擇適合自己的一類水壺,逐一比較……以此類推,第乙個比較的紅色水壺是二叉樹的根節點。
思考題8-5平均排序。乙個陣列是 $k$ 排序的,當且僅當滿足:對於所有的 $i=1,2,...,n-k$ 有:
$$\sum_^a[j]\leq\sum_^a[j]$$
證明乙個陣列是 $k$ 排序的,當且僅當對所有的 $i=1,2,...,n-k$ 有 $a[i]\leq a[i+k]$ 。思路:將上式展開,就能得到這個結論。雖然過程是顯而易見的,但結論卻是反直覺的,事實上乙個 $k$ 排序的陣列並非直覺中的第一印象:平緩上公升偶有波動的曲線,而是 $k$ 個獨立的嚴格排序陣列,交錯穿插著,比如以下這樣的陣列 $11,21,31,12,22,32,13,23,33$ 就是乙個 $k=3$ 排序的陣列,實際上是三個嚴格排序的陣列 $11,12,13$,$21,22,23$,$31,32,33$ 。
給出乙個演算法在 $o(n\lg(n/k))$ 代價內對乙個陣列進行 $k$ 排序。思路:直接均分三份,對每乙份進行快速排序(或者其他什麼代價為 $o(n\lg n)$的排序),然後在 $o(n)$ 代價內合併起來。總代價為 $o(k\cdot(n/k)\cdot\lg(n/k)))=o(n\lg(n/k))$ 。
長度為 $n$ 的 $k$ 排序陣列可以在 $o(n\lg k)$ 代價內排序。思路:相當於 $k$ 個嚴格排序陣列合併成乙個嚴格排序陣列,利用最大堆性質,參照6.5-8。
演算法導論 隨機化的快速排序
public void random quicksort int array,int left,int right public int random position int array,int left,int right param array 待排序的陣列 param left 左邊界 pa...
經典快速排序演算法與隨機快速排序演算法
快速排序用到了分治思想,同樣的還有歸併排序。乍看起來快速排序和歸併排序非常相似,都是將問題變小,先排序子串,最後合併。不同的是快速排序在劃分子問題的時候經過多一步處理,將劃分的兩組資料劃分為一大一小,這樣在最後合併的時候就不必像歸併排序那樣再進行比較。但也正因為如此,劃分的不定性使得快速排序的時間複...
快速排序 演算法導論
對於包含n個數的輸入陣列來說,快速排序是一種最壞情況時間複雜度為o n 的排序演算法。雖然最壞情況時間的複雜度很差,但是快速排序通常是實際排序應用中最好的選擇,因為它的平均效能非常好 它的期望時間複雜度是o nlgn 而且o nlgn 中隱含的常數因子非常小,另外,它還能夠進行原址排序,甚至在虛存環...