在一個由n個元素組成的集合中,第i個順序統計量是該集合中第i小的元素。一箇中位數是它所屬集合的“中點元素”。當n為奇數時,中位數是唯一的,位於i=(n+1)/2處;當n為偶數時,存在兩個中位數,分別位於i=n/2和i=n/2+1處。如果不考慮n的奇偶性,中位數總是出現在i=⌊(n+1)/2⌋處(下中位數)和i=⌈(n+2)/2⌉(上中位數)。
1、最小值和最大值
在一個有n個元素的集合中,需要做n-1次(上界)比較才能找到最小值或最大值。
int MiniValue(const int *A, int len) { int minValue = A[0]; for (int i = 1; i < len; ++i) { if (A[i] < minValue) { minValue = A[i]; } } return minValue; }
那麼如何同時找到最小值和最小值呢?
比較簡單的思路是:只要分別獨立找出最大值和最小值,各需要n-1次比較,共需2n-2次比較。下面給出一個演算法,只需要最多3⌊n/2⌋次比較就可以同時找到最大值和最小值。
思路是:記錄已知的最大值和最小值,對輸出元素成對地進行處理。
(1)首先,將一對輸入元素相互比較,然後將較小的與當前最小值比較,較大的與當前最大值比較,這樣每對元素共需3次比較。
(2)如果n是奇數,將最小值和最大值的初值都設為第一個元素的值,然後成對地處理餘下的元素;如果n是偶數,就對前兩個元素做一次比較,決定最大值和最小值的初值,然後成對地處理餘下的元素。
1 void MinMaxValue(const int *A, int len, int &minValue, int &maxValue) 2 { 3 int i, tmpMin, tmpMax; 4 5 if (len % 2 == 0 && len != 0) 6 { 7 minValue = A[0] < A[1] ? A[0] : A[1]; 8 maxValue = A[0] + A[1] - minValue; 9 i = 2; 10 } 11 else 12 { 13 minValue = A[0]; 14 maxValue = A[0]; 15 i = 1; 16 } 17 18 for (; i < len; i += 2) 19 { 20 if (A[i] < A[i + 1]) 21 { 22 tmpMin = A[i]; 23 tmpMax = A[i + 1]; 24 } 25 else 26 { 27 tmpMin = A[i + 1]; 28 tmpMax = A[i]; 29 } 30 31 if (tmpMin < minValue) 32 { 33 minValue = tmpMin; 34 } 35 36 if (maxValue < tmpMax) 37 { 38 maxValue = tmpMax; 39 } 40 } 41 }
如果n是奇數,共進行3⌊n/2⌋次比較。如果n是偶數,共進行3(n-2)/2+1=3n/2-2次比較。
2、期望為線性時間的選擇演算法
下面介紹一種解決選擇問題的分治演算法。
#include <iostream> using namespace std; int RandomizedPartition(int *A, int low, int high) { int key = A[high], tmp; int i = low - 1, j; for (j = low; j < high; ++j) { if (A[j] <= key) { i = i + 1; tmp = A[i]; A[i] = A[j]; A[j] = tmp; } } A[high] = A[i + 1]; A[i + 1] = key; return i + 1; } int RandomizedSelect(int *A, int low, int high, int i) { if (low == high) { return A[low]; } int q = RandomizedPartition(A, low, high); int k = q - low + 1; if (i == k) { return A[q]; } else if (i < k) { return RandomizedSelect(A, low, q - 1, i); } else { return RandomizedSelect(A, q + 1, high, i - k); } } //test int main() { int A[] = {10, 9, 5, 4, 3, 2, 1, 8, 7, 6}; for (int i = 1; i <= 10; ++i) { cout << RandomizedSelect(A, 0, 9, i) << ' '; } cout << endl; return 0; }
說明:
(1)RandomizedSelect的最壞執行時間為,因為每次劃分時可能總是按餘下的元素中最大的來進行劃分,而劃分操作需要時間。
(2)與快速排序不同的是,快速排序會遞迴處理劃分的兩邊,而RandomizedSelect只處理劃分的一邊,這一差異體現在效能上就是快速排序的期望執行時間為,而RandomizedSelect的期望執行時間為。
3、最壞情況為線性時間的選擇演算法
下面介紹一個最壞情況執行時間為O(n)的選擇演算法Select。
步驟1:將輸入陣列的n個元素劃分為⌊n/5⌋組,每組5個元素,且至多隻有一組由剩下的n mod 5個元素組成。
步驟2:尋找這⌈n/5⌉組中每一組的中位數:首先對每組元素進行插入排序,然後確定每組有序元素的中位數。
步驟3:對步驟2中找出的⌈n/5⌉箇中位數,遞迴呼叫Select以找出其中位數x。
步驟4:利用修改過的Partition版本,按中位數的中位數x對輸入陣列進行劃分。
步驟5:k為劃分的低區元素個數+1。如果i=k,返回x;如果i<k,則在低區遞迴呼叫Select來找出第i小的元素;如果i>k,則在高區遞迴查詢第i-k小的元素。
說明:Select演算法通過對輸入陣列的遞迴劃分來找出所需元素,在該演算法中能夠保證得到對陣列的一個好的劃分。Select使用的也是快速排序的確定性劃分演算法Partition,做的修改是將劃分的主元也作為輸入引數。