一個好的算法往往是在樸素的想法上進行加工改造,我們要求數組中的第k大元素,往往最先想到的想法是先進行降序排序,可以很快的達到答案。但排序在序列元素很多時,無疑是一個浩大的工程。要至少耗費O(logn)的時間複雜度,如果想在線性時間O(n)就想做到求解應該怎麼做呢?
我們先思考排序爲什麼可以解決該問題:
如果我們隨便選定一個元素,假想的認爲它就是我們要找的第k大元素,我們最終要考察,證明的是這個元素在序列降序排序後到底應該處於哪個位置,如果是在第k位,那自然就是第k大元素。說到這裏似乎漏出了點端倪,是啊,我們這樣證明的話並不需要將序列完整的排序,我們只需要將比這個選定元素小的元素排在他的前面,比他大的開到後面去,這時他是不是第k大的元素同樣一目瞭然。
這樣子搞,運氣好了,選的元素正好就是第k大的,那賊舒服,美滋滋。
但人生不如意事十之八九,如果有這個運氣,似乎買彩票都能中獎了。
別慌,雖然我們可能一下子猜不對,但是我們每次猜測都是對歷史進程有幫助的!
假如我們有亂序序列 5 8 3 2 1,我們想求它的第3大元素,我們最開始隨便一猜,認定5就是第k大元素,這個時候我們把比5大的放到5的左邊,比比5小的放到5的右邊
序列變成了 8 5 3 2 1.
額,第一次沒猜中,不過真的不遺憾,我們猜的5,最終位置是2,比3要小,我們至少可以判斷第3大元素肯定不在2及的左邊
我們考察的範圍可以縮小至近乎原來的一半。我們只需要考察3 2 1即可,接下來,我們猜的是3,比他大的放到左邊,比他小的放到右邊,序列沒變!仍然是 8 5 3 2 1 。這時,我們確定了3就是我們猜的第3大元素,進行輸出
for(int lo=0 , hi = n-1; lo<hi;){
int i = lo, j = hi; int pivot = a[lo];
//在區間[lo,hi)裏,比p小的放到p後面,比p大的放在前面
while( i < j){
while( i < j && a[j] <=pivot )j--;a[i] = a[j];//右邊小於pivot的都跳過,大於的提到前面去
while( i < j && a[i] >= pivot)i++;a[j] = a[i];//左邊大於pivot的都跳過,小於的提到後面去
}
a[i] = pivot;//撞車的部分不是i發現不對勁就是j發現了不對勁,元素已經被轉移了
if( i == k){cout<<a[k-1];return 0;}//k-1是因爲下標是從0開始的
else if( k > i)lo = i + 1;
else if( k < i)hi = i - 1;
}