Select 選擇算法 - 編程珠璣(續) 筆記


Select 算法

I 編程珠璣(續)介紹的 Quickselect 算法

選擇 N 個元素中的第 K 小(大)值,是日常場景中常見的問題,也是經典的算法問題.

選取 N 個元素的數組的中的第 K 小(大)值,最簡單的想法是將數組排序后直接選取. 那么這種方法的時間復雜度是O(N log N).

C.A.R.Hoare 提出的 Quickelect 算法的平均時間復雜度達到了 O(N) . 在去遞歸之后, 是原地算法. 這個算法因為其簡潔,高效而被廣泛使用.

算法思路的C++實現如下.

int select(vector<int>& X, int k) {
    int l = 0, u = X.size() - 1;
    while(l < u){
        swap(X[l],  X[rand()%(u-l+1)+l]);
        int m = l;   
        for(int i = l + 1; i <= u; i++)
            if(X[i] < X[l])
                swap(X[++m], X[i]); //m在i遍歷的過程中,是遍歷過的元素中, 小於X[l]的元素的最大下標
        swap(X[l], X[m]);
        if(k <= m) u = m - 1;
        if(k >= m) l = m + 1;
    }
    return X[k];
}
  • k 選定為數組的中位數時,平均所耗的時間最多.
  • 當數組中有大量重復元素,或者是逆序排序的數組時,會增加運行時間. 遇到大量重復的元素時不能很快地縮小 l - u 的范圍. 逆序數組會產生很多的 swap 操作.
  • Worst-case peformance O(N ^ 2)

II 序列輸入時使用的 Heap-Select 算法

考慮一個輸入序列,要求在序列輸入完畢的時候得出這個序列的第 k 大(小)的元素.

要選擇第 k 小的元素時, 我們考慮用一個 k 大小的大頂堆. 對數組從頭開始遍歷(等價於數組線性輸入), 頭 k 個元素用於建立 k 大小的大頂堆. 對於從 k + 1N 的元素. 當該元素小於堆頂元素的時候,將該元素插入到堆中,將堆頂元素出堆. 遍歷(輸入)結束后, 堆頂元素即為我們要找的元素.

相應的選擇第 k 大的元素時, 我們考慮用一個 k 大小的小頂堆.對數組從頭開始遍歷. 頭 k 個元素用於建立 k 大小的小頂堆. 對於從 k + 1N 的元素. 當該元素大於堆頂元素的時候,將該元素插入到堆中,將堆頂元素出堆. 遍歷(輸入)結束后, 堆頂元素即為我們要找的元素.

這樣可得這個算法的時間復雜度為 O(k) + O(N * log k) ==> O(N * log k)

由於要調用空間構造堆,空間復雜度為 O(k)

關於這個算法的正確性,用歸納法, 從已經輸入k的數組中挑選頭k個最大(小)的元素。 然后繼續下去即可。

III 三個元素的中間值

殺雞不用牛刀,三個元素的中間值用簡單的三次比較就可以搞定.

if(X[1] > X[2])
    swap(X[1], X[2]);
if(X[2] > X[3])
    swap(X[2], X[3]);
if(X[1] > X[2])
    swap(X[1], X[2]);  //自此 X[1], X[2], X[3] 從小到大有序.

IV 其他的Select算法

Median of medians 又名 BFPRT算法. 基於Blum, Floyd, Pratt, Rivest and Tarjan 1973年的論文 Time Bounds for Selection. 擁有O(N)worst case performance.

Introselect 則是BFPRT算法和 Quickselect 算法的結合. 默認使用 Quickselect ,在 Quickselect 表現出比較差的運行情況時轉向Median of medians. 從而也能提供O(N)worst case performance.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM