解决的问题:
在一个无需数组中,找到第 k 个大,或者第 k 个小的数
经典思路:类似于快排
我们想要 第 200 小的数
随机选一个数,假设我们选到的数是300,一次遍历,将小于这个数的放左边,等于放中间,大于放右边,得到 x1 和 x2 的位置,x1=300 x2=350
那么 第200小的数 肯定在新数组的 左半部分,我们把左半部分拿出来,再进行这样的划分,最终得到想要的 第 200 小的值
BFPRT
和经典思路类似,但是选取数的策略有所改进
- 分组
每5个数为一组,最后不足5个的成一组 - 组内排序
组内进行排序,共 N/5 组,排序复杂度为 O(1) * O(N) - 拿出每组的中位数,构成新数组
新数组长度为 N/5
如果最后一组 个数为偶数,拿出上中位数 - 递归调用刚才的过程
也就是继续分组,组内排序,拿出每组的中位数构成新数组
新数组,再进行递归调用过程,直到拿出所有数的中位数
例如,共 10 个数,中位数就是 第 5 小的数,也是第 5 大的数,拿这个数进行划分,把小于这个数的放左边,等于的放中间,大于的放右边 - 拿到所有数中位数,如果这个数 是我们想要的,直接返回,如果不是,选左侧或者右侧再进行过程
算法思路
BFPRT(int[] arr, k) {
1.分组
2.组内排序
3.拿出每组中位数,构成新数组 newArr
4.递归:BFPRT(newArr, newArr.length/2)
5.命中,返回,不命中,继续
}
例如数组,3 2 1 4 6 0 1 2 3 5 2 3 4
分组 [3 2 1 4 6] [0 1 2 3 5] [2 3 4]
组内排序 [1 2 3 4 6] [0 1 2 3 5 ] [2 3 4]
拿出每组中位数组成新数组 [3 2 3],
继续分组,发现无法再分,因为组内数据不足 5 个
组内排序 [2 3 3]
直接拿出中位数 3
如果 这个中位数 3
命中,直接返回
不命中:使用 中位数 3,作为基数,然后对原数组 进行:小于 3 的放左边,等于放中间,大于放右边
package BFPRT;
import java.util.Arrays;
public class BFPRT {
public static int getMinKByBFPRT(int[] arr, int k) {
int[] copyArr = copyArray(arr);
return bfprt(copyArr, 0, copyArr.length-1, k-1);
}
/**
* 在 begin 和 end 区间上求第 i 小的数
*
* @param copyArr
* @param begin
* @param end
* @param i
* @return
*/
private static int bfprt(int[] arr, int begin, int end, int i) {
if(begin == end) {
return arr[begin];
}
//求 中位数数组的中位数
int pivot = medianOfMedians(arr, begin, end);
//求 这个中位数 在原数组的 开始位置和结束位置
//就是 把 小于它的放左边 等于放中间 大于放右边
int[] pivotRange = partition(arr, begin, end, pivot);
if(i>=pivotRange[0] && i<=pivotRange[1]) {
//区间命中
return arr[i];
} else if(i<pivotRange[0]) {
//区间不命中,且在左半部分
return bfprt(arr, begin, pivotRange[0]-1, i);
} else {
//区间不明中,且在右半部分
return bfprt(arr, pivotRange[1]+1, end, i);
}
}
private static int[] partition(int[] arr, int begin, int end, int pivot) {
int small = begin - 1;
int current = begin;
int big = end + 1;
while(current != big) {
if(arr[current] < pivot) {
swap(arr, ++small, current++);
} else if(arr[current] == pivot) {
current++;
} else {
swap(arr, current, --big);
}
}
return new int[]{small+1, big-1};
}
private static void swap(int[] arr, int index1, int index2) {
int num = arr[index1];
arr[index1] = arr[index2];
arr[index2] = num;
}
private static int medianOfMedians(int[] arr, int begin, int end) {
int num = end - begin + 1;
int offset = num % 5 == 0? 0:1;
int[] mArr = new int[num/5+offset];
for(int i=0;i<mArr.length;i++) {
int beginI = begin + i * 5;
int endI = beginI + 4;
mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
}
return bfprt(mArr, 0, mArr.length-1, mArr.length/2);
}
private static int getMedian(int[] arr, int begin, int end) {
insertionSort(arr, begin, end);
int sum = end + begin;
int mid = (sum / 2) + (sum % 2);
return arr[mid];
}
private static void insertionSort(int[] arr, int begin, int end) {
for(int i=begin+1;i!=end+1;i++) {
for(int j=i;j!=begin;j--) {
if(arr[j-1] > arr[j]) {
swap(arr, j-1, j);
} else {
break;
}
}
}
}
private static int[] copyArray(int[] arr) {
int[] copyArr = new int[arr.length];
for(int i=0;i<arr.length;i++) {
copyArr[i] = arr[i];
}
return copyArr;
}
public static void main(String[] args) {
int[] sz = {5,9,6,2,4,8,6,2,8,114};
System.out.println(getMinKByBFPRT(sz, 4));
}
}