BFPRT(線性查詢)演算法

2021-07-06 04:55:41 字數 3060 閱讀 6966

bfprt演算法是解決從n個數中選擇第k大或第k小的數這個經典問題的著名演算法,但很多人並不了解其細節。本文將首先介紹求解這個第k小數字問題的幾個思路,然後重點介紹在最壞情況下複雜度仍然為o(n)的bfprt演算法。

關於選擇第k小的數字有許多方法,其效率和複雜度各不一樣,可以根據實際情況進行選擇。

將n個數排序(比如快速排序或歸併排序),選取排序後的第k個數,時間複雜度為o(nlogn)。使用stl函式sort可以大大減少編碼量。

將方法1中的排序方法改為線性時間排序演算法(如基數排序或計數排序),時間複雜度為o(n)。但線性時間排序演算法使用限制較多,不常使用。

維護乙個k個元素的最大堆,儲存當前遇到的最小的k個數,時間複雜度為o(nlogk)。這種方法同樣適用於海量資料的處理。

部分的選擇排序,即把最小的放在第1位,第二小的放在第2位,直到第k位為止,時間複雜度為o(kn)。實現非常簡單。

部分的快速排序(快速選擇演算法),每次劃分之後判斷第k個數在左右哪個部分,然後遞迴對應的部分,平均時間複雜度為o(n)。但最壞情況下複雜度為o(n^2)。

bfprt演算法,修改快速選擇演算法的主元選取規則,使用中位數的中位數的作為主元,最壞情況下時間複雜度為o(n)。

快速選擇演算法就是修改之後的快速排序演算法,前面快速排序的實現與應用這篇文章中講了它的原理和實現。

其主要思想就是在快速排序中得到劃分結果之後,判斷要求的第k個數是在劃分結果的左邊還是右邊,然後只處理對應的那一部分,從而達到降低複雜度的效果。

在快速排序中,平均情況下陣列被劃分成相等的兩部分,則時間複雜度為t(n)=2*t(n/2)+o(n),可以解得t(n)=nlogn。

在快速選擇中,平均情況下陣列也是非常相等的兩部分,但是只處理其中一部分,於是t(n)=t(n/2)+o(n),可以解得t(n)=o(n)。

但是兩者在最壞情況下的時間複雜度均為o(n^2),出現在每次劃分之後左右總有一邊為空的情況下。為了避免這個問題,需要謹慎地選取劃分的主元,一般的方法有:

固定選擇首元素或尾元素作為主元。

隨機選擇乙個元素作為主元。

三數取中,選擇三個數的中位數作為主元。一般是首尾數,再加中間的乙個數或者隨機的乙個數。

為了方便,這裡把前面的**也放在這裡。

int partition(int a, int l, int r) //對陣列a下標從l到r的元素進行劃分

int nthelement(int a, int l, int r, int id) //求陣列a下標l到r中的第id個數

bfprt演算法,又稱為中位數的中位數演算法,由5位大牛(blum 、 floyd 、 pratt 、 rivest 、 tarjan)提出,並以他們的名字命名。參考維基上的介紹median of medians。

演算法的思想是修改快速選擇演算法的主元選取方法,提高演算法在最壞情況下的時間複雜度。其主要步驟為:

首先把陣列按5個數為一組進行分組,最後不足5個的忽略。對每組數進行排序(如插入排序)求取其中位數。

把上一步的所有中位數移到陣列的前面,對這些中位數遞迴呼叫bfprt演算法求得他們的中位數。

將上一步得到的中位數作為劃分的主元進行整個陣列的劃分。

判斷第k個數在劃分結果的左邊、右邊還是恰好是劃分結果本身,前兩者遞迴處理,後者直接返回答案。

首先看演算法的主程式,**如下。小於5個數的情況直接處理返回答案。否則每5個進行求取中位數並放到陣列前面,遞迴呼叫自身求取中位數的中位數,然後用中位數作為主元進行劃分。

注意這裡只利用了中位數的下標,而不關心中位數的數值,目的是方便在劃分函式中使用下標直接進行交換。bfprt演算法執行完畢之後可以保證我們想要的數字是排在了它真實的位置上,所以可以直接使用中位數的下標。

int bfprt(int a, int l, int r, int id) //求陣列a下標l到r中的第id個數

int t = l - 1; //當前替換到前面的中位數的下標

for (int st = l, ed; (ed = st + 4) <= r; st += 5) //每5個進行處理

int pivotid = (l + t) >> 1; //l到t的中位數的下標,作為主元的下標

bfprt(a, l, t, pivotid-l+1);//不關心中位數的值,保證中位數在正確的位置

int m = partition(a, l, r, pivotid), cur = m - l + 1;

if (id == cur) return a[m]; //剛好是第id個數

else if(id < cur) return bfprt(a, l, m-1, id);//第id個數在左邊

else return bfprt(a, m+1, r, id-cur); //第id個數在右邊

}

這裡的劃分函式與之前稍微不同,因為指定了劃分主元的下標,所以引數增加了乙個,並且第一步需要交換主元的位置。**如下:

int partition(int a, int l, int r, int pivotid) //對陣列a下標從l到r的元素進行劃分

這裡簡單分析一下bfprt演算法的複雜度。

劃分時以5個元素為一組求取中位數,共得到n/5個中位數,再遞迴求取中位數,複雜度為t(n/5)。

得到的中位數x作為主元進行劃分,在n/5個中位數中,主元x大於其中1/2*n/5=n/10的中位數,而每個中位數在其本來的5個數的小組中又大於或等於其中的3個數,所以主元x至少大於所有數中的n/10*3=3/10*n個。同理,主元x至少小於所有數中的3/10*n個。即劃分之後,任意一邊的長度至少為3/10,在最壞情況下,每次選擇都選到了7/10的那一部分,則遞迴的複雜度為t(7/10*n)。

在每5個數求中位數和劃分的函式中,進行若干個次線性的掃瞄,其時間複雜度為c*n,其中c為常數。其總的時間複雜度滿足 t(n) <= t(n/5) + t(7/10*n) + c * n。

我們假設t(n)=x*n,其中x不一定是常數(比如x可以為n的倍數,則對應的t(n)=o(n^2))。則有 x*n <= x*n/5 + x*7/10*n + c*n,得到 x<=10*c。於是可以知道x與n無關,t(n)<=10*c*n,為線性時間複雜度演算法。而這又是最壞情況下的分析,故bfprt可以在最壞情況下以線性時間求得n個數中的第k個數。

BFPRT 線性查詢演算法

bfprt演算法解決的問題十分經典,即從某n個元素的序列中選出第k大 第k小 的元素,通過巧妙的分析,bfprt可以保證在最壞情況下仍為線性時間複雜度。該演算法的思想與快速排序思想相似,當然,為使得演算法在最壞情況下,依然能達到o n 的時間複雜度,五位演算法作者做了精妙的處理。演算法步驟 1.將n...

演算法 BFPRT(線性查詢演算法)

bfprt演算法解決的問題十分經典,即從某n個元素的序列中選出第k大 第k小 的元素,通過巧妙的分 析,bfprt可以保證在最壞情況下仍為線性時間複雜度。該演算法的思想與快速排序思想相似,當然,為使得演算法在最壞情況下,依然能達到o n 的時間複雜 度,五位演算法作者做了精妙的處理。演算法步驟 1 ...

BFPRT演算法詳解

在一大堆數中求其前k大或前k小的問題,簡稱top k問題。而目前解決top k問題最有效的演算法即是bfprt演算法,其又稱為中位數的中位數演算法,該演算法由blum floyd pratt rivest tarjan提出,最壞時間複雜度為o n 在首次接觸top k問題時,我們的第一反應就是可以先...