線性時間選擇(Linear Select):這個名字不太好理解,什麼叫線性時間選擇?一句話,在線性時間內完成選擇。通常狀況下是這樣的,咱們想要找出一個數組中的最大值或最小值,那就只須要一次排列,而後輸出第一個或最後一個元素就好了,但若是是要找出一個數組中的第k小的元素呢?算法
在通常狀況下,能夠用RandomizedSelect方法來找出第k小的元素,平均時間是O(n),但在最壞狀況下,所用的時間則是n^2,所以,本文討論的就是在最壞狀況下,如何在O(n)時間內完成選擇。算法的思路整體有些複雜,但每一步其實不難,下面即給你們介紹最壞狀況下的線性時間選擇算法。數組
(1):將n個輸入元素以每組5個地劃分,共劃分出(n/5)個組,每一個組分別進行排列,找出中位數,而後按照每一個組的順序,把每一個組的中位數與整個數組的前(n/5)個數交換;dom
(2):那麼,前(n/5)個數就是各組的中位數了,而後,咱們經過select方法找出這些中位數的中位數,以這個中位數的中位數爲基準,調用partition方法;學習
(3):調用了partition方法後的基準元素正是處於數組的正確位置(前邊的元素都比基準元素小,後邊的元素都比基準元素大),記下基準元素前邊的元素個數leftNum,若是k小於或等於leftNum,則在基準位置前的這部分調用select方法便可,若是在k大於leftNum,則在基準位置後的這部分調用select方法。動畫
下面,我直接把代碼貼出,讀者能夠經過個人註釋來理解每一步的意義。ui
1 private static int select(int[] a,int l,int r,int k){ 2 if(r - l < 75){ 3 insertSort(a, l, r); //用快速排序進行排序 4 return a[l + k - 1]; 5 } 6 int group = (r-l+5)/5; 7 for(int i = 0;i<group;i++){ 8 int left = l+5*i; 9 int right = (l + i * 5 + 4) > r ? r : l + i * 5 + 4; //若是超出右邊界就用右邊界賦值 10 int mid = (left+right)/2; 11 insertSort(a, left, right); 12 swap(a, l + i, mid); // 將各組中位數與前i個 13 } 14 int pivot = select(a,l,l+group-1,(group+1)/2); //找出中位數的中位數 15 int p = partition(a,l,r,pivot); //用中位數的中位數做爲基準的位置 16 int leftNum = p - l; //leftNum用來記錄基準位置的前邊的元素個數 17 if (k == leftNum + 1) 18 return a[p]; 19 else if (k <= leftNum) 20 return select(a, l, p - 1, k); 21 else //若k在基準位子的後邊,則要從基準位置的後邊數起,即第(k - leftNum - 1)個 22 return select(a, p + 1, r, k - leftNum - 1); 23 }
到此你們也能夠看出,這裏的partition方法與前邊講到過的快速排序所用到的partition方法稍有不一樣,參數個數都變了,但其實變化只是很小,只是取消了一開始定義基準位置的步驟而已,代碼以下:spa
1 private static int partition(int[] a,int l,int r,int pivot){ //適用於線性時間選擇的partition方法 2 int i = l; 3 int j = r; 4 while(true){ 5 while(a[i] <= pivot && i < r) 6 ++i; //i一直向後移動,直到出現a[i]>pivot 7 while(a[j] > pivot) 8 --j; //j一直向前移動,直到出現a[j]<pivot 9 if(i >= j) break; 10 swap(a,i,j); 11 } 12 a[l] = a[j]; 13 a[j] = pivot; 14 return j; 15 }
下面是select方法中,若是輸入規模小於75時用到的插入排序算法代碼:code
1 private static void insertSort(int[] a, int law, int high) { //插入排序 2 for (int i = law + 1; i <= high; i++) { 3 int key = a[i]; 4 int j = i - 1; 5 while (j >= law && a[j] > key) { 6 a[j + 1] = a[j]; 7 j--; 8 } 9 a[j + 1] = key; 10 } 11 }
適用於數組元素之間的swap方法以下:blog
1 private static void swap(int[] a,int i,int j){ 2 int temp = a[i]; 3 a[i] = a[j]; 4 a[j] = temp; 5 }
各位可能有個疑問,爲何輸入規模不足75時調用插入排序而不用線性時間選擇呢?那是由於當輸入規模不足75時,由於輸入規模過小,時間複雜度幾乎是一個常量,所以沒有必要用到比較複雜的線性時間選擇算法。排序
我還看到一個比較好懂的學習線性時間選擇的動畫,能形象地看到線性時間選擇的執行過程,連接以下:
若是有不足之處或者對該算法有更好的建議,請提出!