BFPRT算法

381 阅读3分钟

一、作用

在无序的数组中,找到第K小的数。时间复杂度为O(N)。

二、优点。

荷兰国旗问题(快排的partition操作),找中间位置的数是个随机概率的解法,是长期期望的时间复杂度是O(N)。而BFPRT算法则是稳定的O(N)。其改进的方式就是找数处理的方法。

三、思路。

整体的思路

  1. medianOfMedians方法找到中间值。

  2. partition方法,把大于中间值的数放右面,小于中间值的数放左面,等于中间值的数放中间。(此时中间值已经放在了排序好的位置上了,中间值的数组位置就是它的大小值,从0开始)

  3. 根据中间值的范去缩小要递归的范围。直到找到结果。

分部方法的细节和错误点

  1. bfprt方法中,因为是递归函数,不要忘记了终止条件。

     if(start == end){
         return a[start];
     }
    
  2. medianOfMedians方法中,获取五个数的中间值,最后一组不满5需要跟整个数据长度进行比较。(Math.min(endI,end))

     array[i] = getMedian(a,startI,Math.min(endI,end));
    
  3. getMedian的方法,中间下标的计算方法。

     int sum = endI + startI;
     int cur = sum/2 +sum%2;
     return  a[cur];
    
  4. swap方法不要用异或的方式,因为会涉及到自己跟自己异或,导致结果为0.

     异或交换的原理
     疑惑可以理解成不进位的加法,并且遵循交换律和结合律,a^a = 0,a^0 = a
     
     int a;
     int b;
     a = a^b;
     b = a^b;//等于 a^b^b==>a^(b^b)==>a^0==>a
     a = a^b;//等于 a^b^a==>a^a^b==>0^b==>b
    

四、代码实现

public class MyClass {
    public static void main(String[] args) {
        //BFPRT算法
        int[] a = {3,2,1,9,7,6,5};
        //当前第i小的数是从1开始的
        System.out.print(BFPRT(a,4));

        //异或的交换验证
//        int[] b = {1,2};
//        swap(b,0,1);
//        System.out.print(b[0]+" "+b[1]);
        //插入排序的验证
//        insertSort(a,0,a.length-1);
//        System.out.print(Arrays.toString(a));
        //获取中间值的验证
//        System.out.print(getMedian(a,0,a.length-1));
        //中间值的验证
//        System.out.print(medianOfMedians(a,0,a.length-1));
    }

    private static int BFPRT(int[] a, int i) {
        if(i<0||i>a.length-1){
            return -1;
        }
        //当前第i小的数是以0开始的

        return bfprt(a,0,a.length-1,i-1);
    }

    private static int bfprt(int[] a, int start, int end, int k) {
        //递归的终止条件。
        if(start == end){
            return a[start];
        }

        //1.找到需要中间的数
        int median = medianOfMedians(a,start,end);
        //2.partitionc操作,大于median的放右面,小于median的放左面,等于的放中间,把等于的返回。
        int[] res = partition(a,start,end,median);
        //3.减少一半操作数的逻辑
        if(k<res[0]){
            return bfprt(a,start,res[0]-1,k);
        }else if(k>res[1]){
            return bfprt(a,res[1]+1,end,k);
        }else{
            return a[k];
        }
    }
    //插入排序的partition操作,大于的放右面,小于的放左面,等于的放中间。
    private static int[] partition(int[] a, int start, int end, int median) {
        int l = start -1;
        int r = end +1;
        int cur = start;
        while(cur<r){
            if(a[cur]<median){
                swap(a,++l,cur++);
            }else if(a[cur]>median){
                swap(a,--r,cur);
            }else{
                cur++;
            }
        }

        return new int[]{l+1,r-1};
    }
    //不能用异或的交换,因为当i和i1相等的时候会得到0
    private static void swap(int[] a, int i, int i1) {
        int temp = a[i];
        a[i] = a[i1];
        a[i1] = temp;
//        a[i] = a[i]^a[i1];
//        a[i1] = a[i]^a[i1];
//        a[i] = a[i]^a[i1];
    }
    //BFPRT的核心方法,把数组每5个元素分成一组,求的中间大小的值,此时对N/5的样本进行bfprf.
    //奇数个数,取中间,偶数个数取中间两个数的下一个。
    private static int medianOfMedians(int[] a, int start, int end) {
        //1.把整个范围以5份分成小份的数组
        int arrayCount = (end - start +1)/5+(end - start +1)%5==0?0:1;
        int[] array = new int[arrayCount];
        for(int i=0;i<array.length;i++){
            int startI = start+i*5;
            int endI = startI +4;
            array[i] = getMedian(a,startI,Math.min(endI,end));
        }
        return bfprt(array,0,array.length-1,array.length/2);
    }

    //插入排序后,求的中间的值。
    //奇数就中间的值,偶数中间两个数里的下一个。
    private static int getMedian(int[] a, int startI, int endI) {
        insertSort(a,startI,endI);
        int sum = endI + startI;
        int cur = sum/2 +sum%2;
        return  a[cur];
    }
    //插入排序,优化了交换的逻辑。
    private static void insertSort(int[] a, int startI, int endI) {
        for(int i = startI+1;i<=endI;i++){
            int j;
            int res = a[i];
            for(j=i-1;j>=startI;j--){
                if(a[j]>res){
                    a[j+1] = a[j];
                }else{
                    break;
                }
            }
            a[j+1] = res;
        }
    }


}