算法之線性時間選擇(最壞狀況下)

線性時間選擇(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時,由於輸入規模過小,時間複雜度幾乎是一個常量,所以沒有必要用到比較複雜的線性時間選擇算法。排序

  我還看到一個比較好懂的學習線性時間選擇的動畫,能形象地看到線性時間選擇的執行過程,連接以下:

http://resource.jingpinke.com/details?uuid=ff808081-22e8911b-0122-e8912643-048d&objectId=oid:ff808081-22e8911b-0122-e8912643-048e

  若是有不足之處或者對該算法有更好的建議,請提出!

相關文章
相關標籤/搜索