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);
}
}
}

浙公网安备 33010602011771号