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算法_第1张图片
本算法的最坏时间复杂度为,值得注意的是通过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;i5;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(iwhile(array[j]>=pivot&&iarray[i]=array[j];
            while(array[i]<=pivot&&iarray[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;iarray[left+i];
        Arrays.sort(temp);//当数量少于一定值的时候sort是插入排序。
        for(int i=0;iarray[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;
    }
}

你可能感兴趣的:(算法)