相信計算機專業的同學應該都對快速排序有或多或少的了解。
設定此模組是因為,線性時間選擇topk與快速排序的思想有相通之處,可以輔助我們理解。
快速排序的思路:
設定乙個瞭望元素(劃分元素)。
以此元素為基礎,將工作區間 [l, r] 內的所有元素分割成兩部分。劃分元素以左均比其小,劃分元素以右均比其大。
對分割元素左右兩側遞迴快排。
我們可以將快速排序演算法分為以上三步,概括來講就是三個函式:
qsort (int l, int r),它表示給 [l, r] 範圍內的數排序
getpartition (int l, int r),他表示獲取 [l, r] 範圍內的分割元素下標
partition (int l, int r, int parelem),他表示在parelem號元素為分割元素分割 [l, r]
我們也都知道的一件事是,快速排序的效能不穩定。
而導致其效能不穩定的關鍵因素就是分割元素的選取。
如果分割元素每次都可以將工作區間近乎等分的話,就可以有o(n * lgn)的優秀效能。
如果分割元素每次都取在工作區間的最大值或最小值的話,那就要付出o(n ^ 2)的代價。
到此為止,我們複習了快速排序的思路和分割元素的選取對效能有關鍵影響的結論。
希望大家謹記。
帶著剛剛的收穫,我們來看topk問題。此後以在所有元素中選取第k大的元素為例。
試想我們已經按照快速排序的方法,以分割元素parelem將區間分成了兩部分。
即parelem左邊都是比他小的元素,parelem右邊都是比他大的元素。
那整個[l, r] 區間就可以被分成兩部分[l, parelem - 1]和[parelem, r]。
遞迴狀態轉移
我們可以確定地是,問題的解不是在左半部分區間,就是在右半部分區間,而且右半部分區間地數字都比左半部分的數字大。
發現聯絡了沒有,快速排序的getpartition和partition兩個函式可以為求解topk服務,我們需要做的只是將qsort換成的對應的selectk即可。
還記得我們在上一模組得出的重要結論嗎?
分割元素的選取對效能有關鍵影響。
那麼怎麼選取分割元素比較好呢?
分割元素將區間分成的兩部分長度越接近越好。
也就等價於,分割元素的數值大小是區間內數值的中位數最好。
那麼我們如何才能實現這一需求呢?
傳統選取分割元素的方式:選取區間內第乙個元素為分割元素。
優點:操作簡單;缺點:不穩定
隨機選取分割元素的方式:通過乙個隨機數種子選取分割元素。
優點:隨機選取穩定性比第一種好了很多;缺點:計算機實現的偽隨機仍存在不穩定因素
我們之前得到的分割元素數值大小最好是中位數,所以最好的辦法應該是和中位數有關的。
當然,選取中位數的演算法和topk的複雜度是一致的了,顯然不值得。
所以我們在此使用的選取分割元素的方法是和中位數有關,但他選取的不是整個工作區間內的中位數。
getpartition函式描述
將整個工作區間 [l, r] 按照每size個元素一組,分成多組。(size表示分組的大小,是乙個正整數)
對每個組進行排序。(因為每個組的元素個數是size,是乙個常數,所以對其進行排序操作的時間複雜度也是常數,只會影響整個topk演算法複雜度的係數,是值得的的)
將每個小組的中位數放到一組。(排完序就有中位數嘍)
對第三步形成的中位數小組進行排序,並選取中位數
以第四步選取的中位數作為分割元素
比較複雜對不對?但這就是我們線性選擇topk的關鍵所在了。
選取了合適的分割元素後,就可以快樂的按照我們之前提到的partition和selectk,遞迴呼叫求解了。
num是存放數字的陣列
size是分組時的小組長度
min_len是指,當我們的區間長度縮小到該程度後,就不必費勁的去遞迴求解了。
可以直接排序求解
int
getpartition
(int l,
int r)
// 選擇所有組別中位數中的中位數,即分割元素
int parelem =
selectk
(l, l + groupnum, groupnum /2)
;// 返回分割元素下標
return parelem;
}}
int
partition
(int l,
int r,
int parelem)
num[l]
= num[j]
; num[j]
= x;
// 返回做完分割後的分割元素下標
return j;
}
int
selectk
(int l,
int r,
int k)
// 獲得分割元素,並以該分割元素完成分割
int parelem =
getpartition
(l, r)
; parelem =
partition
(l, r, parelem)
;// rl表示區間[parelem, r]的長度
// 如果求第k小就應該看左區間長度了
int rl = r - parelem +1;
if(k <= rl)
else
}
我們已經知道該問題的解法與快排及其相似,partition和getpartirtion兩個函式甚至是完全一樣的。那為什麼快排的複雜度是o(n * lgn),而本演算法的複雜度是o(n)呢?
因為快排是分治,而本演算法是減治。
這兩者有何分別呢?
在快排中,選出分割元素並完成分割後,我們要做什麼呢?遞迴對分割元素左右兩邊求解。也就是說,在快排中我們完成分割後,分割元素左右兩側仍然是需要求解的,因為我們要進行的是排序。這就是分治,將整個問題集合分成小問題來逐一解決。
那在topk中,選出分割元素並完成分割後,我們要做什麼呢?遞迴對分割元素左側或右側求解。也就是說,左右兩側我們只需求解一側即可。因為這是選擇性問題,我們可以確定答案在某一側,那另一側就不需要求解了。這就是減治,將問題集合分開後選擇乙個子集和求解。
線性時間選擇 TOP K
問題描述 找出乙個陣列中第k小的元素,時間複雜度為o n 解法 1.首先大家都會想到的解法是排序,之後找出第k個元素,但是排序的時間複雜度不符合要求,或者需要額外的空間。2.利用快排的思想,以樞紐 隨機得到 為界,將陣列分為2部分,一部分小於等於這個樞紐值,一部分大於這個樞紐值,與快排不同的是,我們...
Top k問題(線性時間選擇演算法)
問題描述 給定n個整數,求其中第k小的數。分析 顯然,對所有的資料進行排序,即很容易找到第k小的數。但是排序的時間複雜度較高,很難達到線性時間,雜湊排序可以實現,但是需要另外的輔助空間。演算法 linearselect s,k 輸入 陣列s 1 n 和正整數k,其中1 k n 輸出 s中第k小的元素...
線性時間選擇問題
將n個元素劃分成n 5組,每組5個元素,只可能有一組不是5個元素。再用氣泡排序法,將每組內的五個元素排好序,取出其中位數,共n 5個。然後遞迴呼叫select方法找出這n 5個數中的中位數。若n 5是偶數,就找其最大的數。以這個元素作為劃分標準。判斷k與n的位置,再進行下一步的劃分。以 8,31,6...