解决的问题:在一个无序数组中找到第k小的数
快排解法:
- 随机选择一个数,利用荷兰国旗问题,将数据分为小于等于大于三块
- 利用区分好的数组计算长度,看是否命中,否则看是在哪个块中,在确定块中继续划分,重新回到1。
对于这种解法而言,如果最差情况是O(N^2),一般情况下,用master公式计算出,最好是O(N)。最后的数学期望是O(N)。
暴力解法:
将整个数组全排列,形成一个有序数组,然后再找到第k个元素。
BFPRT算法:
BFPRT算法跟快排的算法只有在选取划分值的情况上不同,其他全部一样。
BFPRT算法:
- 分组(假设每五个一组,最后剩余的不到五个的一组)
- 分组之后每个小组之内排序,跨组不排序,五个数排序,总共需要划分的时间复杂度为O(N)。
- 将每个组的中位数拿出,构成新的数组,此时新数组长度为N/5(最后不到五个的可以拿上中位数,也可以拿下中位数)
- 调用BFPRT算法,此时递归过程中不再寻找k项,而是选择中间的中位数
- 下面就是利用上述num,进行荷兰国旗问题排序
每一步的时间复杂度:
第一步复杂度O(1),
第二步复杂度O(n),
第三部复杂度O(n),
第四步T(N/5),求出p
第五步O(N),
第六步可以确定好是正好还是前往左/右,
此时左边和右边的规模可以估计出来,而不再是未知的。
此时可以估计出至少有多少个数比p要大,或者比p小。
通过计算可以得知,按照三次排序,第一次排序N/5个人被挑选出来,再进行排序,此时N/10比数值大,比中位数大的数值此时加入到原始数组中,所以此时N/10+2N/10=3N/10,所以此时最多有7N/10比p大或者小。
代码实现
//用BFPRT方法得到第k个最小的值
public static int getMinKthByBFPRT(int[] arr,int k ){
int[] copyArr = copyArray(arr);
//得到数组中第k-1位置上的值就是第k小的值
return bfprt(copyArr,0,copyArr.length-1,K-1);
}
//bfprt方法主体部分
public static int bfprt(int[] arr,int begin,int end,int i){
if(begin == end){
return arr[begin];
}
//求中位数的中位数
int pivot = medianOfMedians(arr,begin,end);
//求完第二轮的中位数之后就开始进行划分
int[] privotRange = partition(arr,begin,end,pivot);
//正好i位置等于相等部分则返回
if(i>= pivotRange[0] && i <= pivotRange[1]){
return arr[i];
//i小于排序起始位置的情况
}else if(i<pivotRange[0]){
return bgprt(arr,begin,pivotRange[0]-1,i);
}else{
//i大于终止位置的情况
return bfprt(arr,pivotRange[1]+1,end,i);
}
}
public 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+offest];
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);
}
//partition是实现荷兰国旗问题,将大于小于等于三类划分开
public static int[] partition(int[] arr,int begin,int end,int privotValue){
int small = begin -1;
int cur = begin;
int big = end+1;
while(cur != big){
if(arr[cur]< pivotValue){
swap(arr,++small,cur++);
}else if(arr[cur]>pivotValue){
swap(arr,cur,--big);
}else{
cur++;
}
}
//range返回两个值,第一个是起始位置,第二个是排序的终止位置
int[] range = new int[2];
range[0] = samll+1;
range[1] = big-1;
return range;
}