bfprt算法

首先讲一下bfprt算法是干嘛的?

bfprt算法是用来求数组中第k小的元素的算法,bfprt算法可以在O(n)时间内求出答案。

算法思想:
对于求数组中第k小的元素的问题,我们已经有很好的常规算法了,这个算法在最好的情况下时间复杂度是O(n),但在最坏的情况下是O(n^2)的,其实bfprt算法就是在这个基础上改进的。

常规解法:
我们随机在数组中选择一个数作为划分值(number),然后进行快排的partation过程(将小于number的数放到数组左边,等于number的数放到数组中间,大于number的数放到数组右边),然后判断k与等于number区域的相对关系,如果k正好在等于number区域,那么数组第k小的数就是number,如果k在等于number区域的左边,那么我们递归对左边再进行上述过程,如果k在等于number区域的右边,那我们递归对右边再进行上述过程。

对于常规解法,我们分析一下它的时间复杂度:

递归函数复杂度计算:

T(N)=a*T(N/b)+o(N^d);
当log(b,a)>d时 复杂度为O(N^log(b,a));
当log(b,a)=d时 复杂度为O(N^d*log(2,N));
当log(b,a)<d时 复杂度为O(N^d);
N为样本数据个数 即数组中元素的个数。
N/b为将这N个数划分为更小的部分,每个部分的样本数据个数,一般为均分,那么b等于2。
a为划分为多个部分后,小部分执行的次数。
d为递归函数调用完成后剩下的操作的时间复杂度。

对于最好的情况:每次所选的number正好在数组的正中间,那么上式中a等于1,b等于2,对于partation过程,时间复杂度是O(n),所以d等于1。所以T(N)= T(N/2)+ O(N),此时 log( 2 , 1 ) < 1,故时间复杂度为O(N)。

对于最坏情况:每次所选的number正好在数组最边上,那么时间复杂度为O(N ^ 2).

bfprt算法就是在这个number上做文章,bfprt算法能够保证每次所选的number在数组的中间位置,那么时间复杂度就是O(N)。

bfprt解法:
bfprt解法和常规解法唯一不同的就是在number的选取上,其他地方一模一样,所以我们只讲选取number这一过程。

第一步:我们将数组每5个相邻的数分成一组,后面的数如果不够5个数也分成一组。

第二步:对于每组数,我们找出这5个数的中位数,将所有组的中位数构成一个median数组(中位数数组)。

第三步:我们再求这个中位数数组中的中位数,此时所求出的中位数就是那个number。

第四步:通过这个number进行partation过程,下面和常规解法就一样了。

接下来我们分析一下为什么bfprt算法每次选number的时候都能够在数组的中间位置。

 

我们假设这就是分出来的每5个数的小组,每一列代表一个小组。

 

 

图中红框内的数我们假设就是每一组的中位数。我们假设总数组的数字个数是N,那么中位数数组中数字的个数就是 N / 5 。

 

 

 我们假设用蓝框框起来的数是中位数数组的中位数(divide),那么由中位数的性质可知,中位数数组中有一半的数比这个divide大,所以总共有 N / 10个数比这个divide大。

 

用紫色框框出的数肯定也是比divide大,所以至少有N / 10 + ( 2*N ) / 10 = ( 3*N ) / 10 个数比divide大,那么以divide为划分的partation过程能够使得divide在数组的靠近中间的位置,最坏情况也能够在数组的 ( 3*N ) / 10 或者 ( 7*N ) / 10 的位置。时间复杂度为O(N)。

// arr[L..R]  如果排序的话,位于index位置的数,是什么,返回
    public static int bfprt(int[] arr, int L, int R, int index) {
        if (L == R) {
            return arr[L];
        }
        int pivot = medianOfMedians(arr, L, R);
        int[] range = partition(arr, L, R, pivot);
        if (index >= range[0] && index <= range[1]) {
            return arr[index];
        } else if (index < range[0]) {
            return bfprt(arr, L, range[0] - 1, index);
        } else {
            return bfprt(arr, range[1] + 1, R, index);
        }
    }

    // arr[L...R]  五个数一组
    // 每个小组内部排序
    // 每个小组中位数领出来,组成marr
    // 求marr中的中位数,返回
    public static int medianOfMedians(int[] arr, int L, int R) {
        int size = R - L + 1;
        int offset = size % 5 == 0 ? 0 : 1;
        int[] mArr = new int[size / 5 + offset];
        for (int team = 0; team < mArr.length; team++) {
            int teamFirst = L + team * 5;
            // L ... L + 4
            // L +5 ... L +9
            // L +10....L+14
            mArr[team] = getMedian(arr, teamFirst, Math.min(R, teamFirst + 4));
        }
        // marr中,找到中位数 使用bfprt算法
        // marr(0, marr.len - 1,  mArr.length / 2 )
        return bfprt(mArr, 0, mArr.length - 1, mArr.length / 2);
    }

    public static int getMedian(int[] arr, int L, int R) {
        insertionSort(arr, L, R);
        return arr[(L + R) / 2];
    }
public static void insertionSort(int[] arr, int L, int R) {
for (int i = L + 1; i <= R; i++) {
for (int j = i - 1; j >= L && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
 

 

posted @ 2022-11-16 10:14  keep每天进步一点点  阅读(121)  评论(0)    收藏  举报