一、作用
在无序的数组中,找到第K小的数。时间复杂度为O(N)。
二、优点。
荷兰国旗问题(快排的partition操作),找中间位置的数是个随机概率的解法,是长期期望的时间复杂度是O(N)。而BFPRT算法则是稳定的O(N)。其改进的方式就是找数处理的方法。
三、思路。
整体的思路
-
medianOfMedians方法找到中间值。
-
partition方法,把大于中间值的数放右面,小于中间值的数放左面,等于中间值的数放中间。(此时中间值已经放在了排序好的位置上了,中间值的数组位置就是它的大小值,从0开始)
-
根据中间值的范去缩小要递归的范围。直到找到结果。
分部方法的细节和错误点
-
bfprt方法中,因为是递归函数,不要忘记了终止条件。
if(start == end){ return a[start]; } -
medianOfMedians方法中,获取五个数的中间值,最后一组不满5需要跟整个数据长度进行比较。(Math.min(endI,end))
array[i] = getMedian(a,startI,Math.min(endI,end)); -
getMedian的方法,中间下标的计算方法。
int sum = endI + startI; int cur = sum/2 +sum%2; return a[cur]; -
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;
}
}
}