BFPTR算法

通常,我們需要在一大堆數中求前K大的數,或者求前K小的。比如在搜索引擎中求當天用戶點擊次數排名
前10000的熱詞;在文本特徵選擇中求IF-IDF值按從大到小排名前K個的等等問題,都涉及到一個核心問
題,即TOP-K問題。

通常來說,TOP-K問題可以先對所有數進行快速排序,然後取前K大的即可。但是這樣做有兩個問題。

(1)快速排序的平均複雜度爲,但最壞時間複雜度爲,不能始終保證較好的複雜度。
(2)我們只需要前K大的,而對其餘不需要的數也進行了排序,浪費了大量排序時間。

除這種方法之外,堆排序也是一個比較好的選擇,可以維護一個大小爲K的堆,時間複雜度爲。

我們的目的是求前K大的或者前K小的元素,實際上有一個比較好的算法,叫做BFPTR算法,又稱爲中位數
的中位數算法,它的最壞時間複雜度爲,它是由Blum、Floyd、Pratt、Rivest、Tarjan提出。
該算法的思想是修改快速選擇算法的主元選取方法,提高算法在最壞情況下的時間複雜度。

在BFPTR算法中,僅僅是改變了快速排序Partion中的pivot值的選取,在快速排序中,我們始終選擇第一個元
素或者最後一個元素作爲pivot,而在BFPTR算法中,每次選擇五分中位數的中位數作爲pivot,這樣做的目的
就是使得劃分比較合理,從而避免了最壞情況的發生。算法步驟如下

(1)將輸入數組的個元素劃分爲組,每組5個元素,且至多隻有一個組由剩下的個元素組成。
(2)尋找個組中每一個組的中位數,首先對每組的元素進行插入排序,然後從排序過的序列中選出中位數。
(3)對於(2)中找出的箇中位數,遞歸進行步驟(1)和(2),直到只剩下一個數即爲這個元素
的中位數,找到中位數後並找到對應的下標。
(4)進行Partion劃分過程,Partion劃分中的pivot元素下標爲。
(5)進行高低區判斷即可。
這裏寫圖片描述
本算法的最壞時間複雜度爲,值得注意的是通過BFPTR算法將數組按第K小(大)的元素劃分爲兩部分,而
這高低兩部分不一定是有序的,通常我們也不需要求出順序,而只需要求出前K大的或者前K小的。

另外注意一點,求第K大就是求第n-K+1小,這兩者等價。TOP K問題在工程中有重要應用,所以很有必要掌握。

import java.util.*;
public class FindK 
{
    private static int array[];
    public static void main(String[] args) 
    {   
        Scanner cin=new Scanner(System.in);
        array=new int[20];
        System.out.println("原序列");
        for(int i=0;i<20;i++)
        {
            array[i]=20-i;
            System.out.print(array[i]+" ");
        }
        System.out.println();
        System.out.println("請輸入你要搜索的數字");
        int k=cin.nextInt();
        System.out.println("第個"+k+"數是"+BFPTR(array,0,19,k));
        System.out.println("處理後的序列:");
        for(int i=0;i<20;i++)
        {
            System.out.print(array[i]+" ");
        }
    }

    private static int BFPTR(int[] array,int left,int right,int k)
    {
        int mid=findMid(array,left,right);//尋找中位數的中位數並把它放到第一位去

        int i=partion(array,left,right,mid);//快排固定中位數的位置,浮動的並不一定是理想狀態的位子
        int num=i-left+1;     //代表的是從中位數的位子到前面有多少個數
        if(num==k)
            return array[i];
        if(num>k)                         //如果k在num個數裏面就深入查找k
            return BFPTR(array,left,i-1,k);
        //k是第k個數。是數量!所以是可以變動的。只要數到相應個數就好
        return BFPTR(array,i+1,right,k-num);//如果k在num個數外面就直接挪到後面去查找k
    }

    //尋找中位數的中位數
    private static int findMid(int[] array,int left,int right)
    {
        if(right==left)
            return left;
        int i=0,n=0;
        for(i=left;i<right-5;i+=5)
        {
            insertSort(array,i,i+4);
            n=i-left;
            swap(array, i+n/5,i+2);
        }
        //處理剩餘元素
        int num=right-i+1;
        if(num>0)
        {
            insertSort(array,i,i+num-1);
            n=i-left;
            swap(array,left+n/5,i+num/2);
        }
        n/=5;
        if(n==left)
            return left;
        return findMid(array,left,left+n);
    }

    //定位後,快排劃分兩邊
    private static int partion(int[] array,int left,int right,int p)
    {
        swap(array,p,left);
        int i=left;
        int j=right;
        int pivot=array[left];
        while(i<j)
        {
            while(array[j]>=pivot&&i<j)
                j--;
            array[i]=array[j];
            while(array[i]<=pivot&&i<j)
                i++;
            array[j]=array[i];
        }
        array[i]=pivot;
        return i;
    }

    //部分插入排序
    private static void insertSort(int[] array,int left,int right)
    {
        int[] temp=new int[right-left+1];
        for(int i=0;i<temp.length;i++)
            temp[i]=array[left+i];
        Arrays.sort(temp);//當數量少於一定值的時候sort是插入排序。
        for(int i=0;i<temp.length;i++)
            array[left+i]=temp[i];
    } 
    //用於交換
    private static void swap(int[] array,int i,int j)
    {
        int temp;
        temp=array[i];
        array[i]=array[j];
        array[j]=temp;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章