BFPRT

解决的问题:
在一个无需数组中,找到第 k 个大,或者第 k 个小的数

经典思路:类似于快排
我们想要 第 200 小的数
随机选一个数,假设我们选到的数是300,一次遍历,将小于这个数的放左边,等于放中间,大于放右边,得到 x1 和 x2 的位置,x1=300 x2=350
那么 第200小的数 肯定在新数组的 左半部分,我们把左半部分拿出来,再进行这样的划分,最终得到想要的 第 200 小的值

BFPRT
和经典思路类似,但是选取数的策略有所改进

  1. 分组
    每5个数为一组,最后不足5个的成一组
  2. 组内排序
    组内进行排序,共 N/5 组,排序复杂度为 O(1) * O(N)
  3. 拿出每组的中位数,构成新数组
    新数组长度为 N/5
    如果最后一组 个数为偶数,拿出上中位数
  4. 递归调用刚才的过程
    也就是继续分组,组内排序,拿出每组的中位数构成新数组
    新数组,再进行递归调用过程,直到拿出所有数的中位数
    例如,共 10 个数,中位数就是 第 5 小的数,也是第 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));
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值