第九章 中位元和順序統計學
9.2 以期望線性時間做選擇
上一講是找出最小值,同時找出最大值最小值,以及找出次小值的問題。隨意選擇數組中第i小(大)的元素看起來要比找最小值的簡單選擇問題要複雜一些。但是令人驚奇的是,兩種問題的漸近已耗用時間卻是相同的,都是O(n)。演算法導論上介紹了一種用來解決選擇問題的分治演算法,即RANDOMIZED-SELECT演算法。本演算法以快速排序演算法的分割演算法為基礎,如同在快速排序中一樣,此演算法的思想也是對輸入數組進行遞迴劃分。但與快速排序不同的是,快速排序會遞迴處理劃分的兩邊,而RANDOMIZED-SELECT只處理劃分的某一邊。這一差異在演算法的分析中就體現出來了:快速排序的期望已耗用時間是O(nlgn)。而RANDOMIZED-SELECT的期望已耗用時間為O(n)。下面是RANDOMIZED-SELECT演算法:
RANDOMIZED-SELECT(A, p, r, i) P109
1 if p = r2 then return A[p]3 q ← RANDOMIZED-PARTITION(A, p, r)4 k ← q - p + 15 if i = k6 then return A[q]7 elseif i < k8 then return RANDOMIZED-SELECT(A, p, q - 1, i)9 else return RANDOMIZED-SELECT(A, q + 1, r, i - k)
RANDOMIZED-SELECT利用了RANDOMIZED-PARTITION程式。所以,本演算法也是一個隨機演算法,因為它的行為部分地由隨機數產生器的輸出來決定。本演算法返回A[p..r]中的第i小的元素。
演算法第三行的RANDOMIZED-PARTITION執行之後,數組A[p..r]被劃分為兩個子數組A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每個元素都小於或等於A[q],A[q+1..r]中的每個元素都大於A[q]。
第四行計算A[p..r]的元素個數k,即處於低劃分區的元素的個數加上一個主要元素。然後第五行檢查A[q]是不是第i小的元素。如果是,就返回A[q]。否則,演算法要確定第i小的元素落在兩個子數組A[p..q-1]和A[q+1..r]中的哪一個之中。如果i<k,則在低區,如i>k,則在高區。然後遞迴選擇。
演算法的最壞已耗用時間為O(n2),即使是要選擇最小元素。
《編程之美》中的2.3 尋找發貼“水王”就可以用順序統計量來解答。
C++代碼
1 #include <iostream> 2 #include <ctime> 3 #include <cstdlib> 4 5 using namespace std; 6 7 void swap(int* x, int* y) 8 { 9 int temp;10 temp = *x;11 *x = *y;12 *y = temp;13 }14 15 inline int random(int x, int y)16 {17 srand((unsigned)time(0));18 int ran_num = rand() % (y - x) + x;19 return ran_num;20 }21 22 int partition(int* arr, int p, int r)23 {24 int x = arr[r];25 int i = p - 1;26 for(int j = p; j < r; j++)27 {28 if (arr[j] <= x)29 {30 i++;31 swap(arr[i], arr[j]);32 }33 }34 swap(arr[i + 1], arr[r]);35 return ++i;36 }37 38 int randomizedpartition(int* arr, int p, int r)39 {40 int i = random(p, r);41 swap(arr[r], arr[i]);42 return partition(arr, p, r);43 }44 45 int randomizedSelect(int* arr, int p, int r, int i)46 {47 if(p == r)48 {49 return arr[p];50 }51 int q = randomizedpartition(arr, p, r);52 int k = q - p + 1;53 if(i == k)54 {55 return arr[q];56 }57 else if(i < k)58 {59 return randomizedSelect(arr, p, q - 1, i);60 }61 else62 return randomizedSelect(arr, q + 1, r, i - k);63 }64 65 int main()66 {67 int arr[] = {1, 3, 5, 23, 64, 7, 23, 6, 34, 98, 100, 9};68 int i = randomizedSelect(arr, 0, 11, 4);69 cout << i << endl;70 return 0;71 }