BFPTR算法,又称为中位数的中位数算法,它的最坏时间复杂度为O(n)
TOP-K问题
- 将n个数排序(比如快速排序或归并排序),选取排序后的第k个数,时间复杂度为O(nlogn)。
- 维护一个k个元素的最大堆,存储当前遇到的最小的k个数,时间复杂度为O(nlogk)。
- 部分的选择排序,即把最小的放在第1位,第二小的放在第2位,直到第k位为止,时间复杂度为O(kn)
- 部分的快速排序(快速选择算法),每次划分之后判断第k个数在左右哪个部分,然后递归对应的部分,平均时间复杂度为O(n)。但最坏情况下复杂度为O(n^2)。
固定选择首元素或尾元素作为主元。
随机选择一个元素作为主元。
三数取中,选择三个数的中位数作为主元。一般是首尾数,再加中间的一个数或者随机的一个数。
- BFPRT算法,修改快速选择算法的主元选取规则,使用中位数的中位数作为主元,最坏情况下时间复杂度为O(n)。
BFPTR算法
在BFPTR算法中,每次选择五分中位数的中位数作为pivot,这样做的目的就是使得划分比较合理,从而避免了最坏情况的发生。算法步骤如下:
- 将输入数组的 n 个元素划分为 n/5 组,每组5个元素,且至多只有一个组由剩下的 n%5 个元素组成。
- 寻找 n/5 个组中每一个组的中位数,首先对每组的元素进行插入排序,然后从排序过的序列中选出中位数。
- 对于(2)中找出的 n/5 个中位数,递归进行步骤(1)和(2),直到只剩下一个数即为这 n/5 个元素的中位数,找到中位数后并找到对应的下标 P。
- 进行Partion划分过程,Partion划分中的pivot元素下标为 P。
- 进行高低区判断即可。
/**
* -在数组中选出第k大元素平均复杂度为O(N)的算法,俗称”中位数之中位数算法”
*/
public class BFPTR {
int scope = 5;
public static void main(String[] args) {
BFPTR app = new BFPTR();
int[] arr = new int[]{5,6,8,7,3,2,1,4,10,20,39,12,21,19,27,23,25,6,7};
app.bfptrSort(arr);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i] + " ");
}
}
public int[] bfptrSort(int[] arr){
return bfptrSort(arr,0,arr.length-1);
}
public int[] bfptrSort(int[] arr,int s,int e){
if(s<e){
int king = findKing(arr,s,e);
int p = partition(arr,s,e,king);
bfptrSort(arr,s,p-1);
bfptrSort(arr,p+1,e);
}
return arr;
}
public int findKing(int[] arr,int s,int e){
int[] medias = findMedias(arr,s,e);
insertSort(medias,0,medias.length-1);
int king = medias[(medias.length-1) >> 1];
return king;
}
/**
* -对{arr[e]~arr[s]}这一段分组求中为数
*/
public int[] findMedias(int[] arr,int s,int e){
//通过给定的start和end,先定义好有几个中位数
int[] res = new int[(int)Math.ceil((e-s+1.0)/scope)];
int k=0;
//如果e-s<scope说明可以直接对arr的s~e这一段进行快排,从
//而得到中位数;
if(e-s<scope){
insertSort(arr,s,e);
res[k++] = arr[(s+e) >> 1];
}
else{
//如果e-s>=scope,那么设置步长为scope的窗口,逐步进
//行寻找每一段的中位数。
int i=s,j=0,splitStart =0 ,splitEnd = 0;
while(i++<=e){
if(++j == scope){
// 窗口的起始位置;
splitStart = k*scope+s;
//窗口的结束位置;
splitEnd = (k+1)*scope+s-1;
insertSort(arr,splitStart,splitEnd);
res[k++] = arr[(splitStart+splitEnd) >> 1];
j=0;
}
}
//说明(e-s)%scope !=0, 需要把最后一小段加进去
if(splitEnd < e){
insertSort(arr,splitEnd+1,e);
res[k++] = arr[(splitEnd+1+e) >> 1];
}
}
return res;
}
/**
* -快排,对{arr[s]~arr[e]}这一段进行快排
*/
public int[] insertSort(int[] arr,int s,int e){
for(int i=s;i<=e;i++){
int key = i;
for(int j=i-1;j>=s;j--){
if(arr[key] < arr[j]){
swap(arr,key,j);
key --;
}
else{
break;
}
}
}
return arr;
}
public void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] =tmp;
}
/**
* -给定一个king,对于{arr[s]~arr[e]}这一段进行partition
*/
public int partition(int[] arr,int s,int e,int king){
int i = s;
int j = e;
int kingIndex = 0;
for(int k =s;k<=e;k++){
if(arr[k]==king){
kingIndex = k;
break;
}
}
while(i<j){
while(arr[j]>=king && j>kingIndex){
j--;
}
swap(arr,j,kingIndex);
kingIndex=j;
while(arr[i]<= king && i<kingIndex){
i++;
}
swap(arr,i,kingIndex);
kingIndex=i;
}
return i;
}
}