关于Top-K问题,我们最容易想到的常规算法是是先排序,再返回第K个元素,快速排序的平均复杂度为O(nlogn),最坏时间复杂度为O(n2),不能始终保证较好的复杂度。这里我们只需要Top-K个元素或者是第k个元素,对其他元素并不关心,对n个数全部进行排序显然是不合理的,那么有没有一种更快的算法呢?
目前解决TOP-K问题最有效的算法即是BFPRT算法,其又称为中位数的中位数算法,该算法由Blum、Floyd、Pratt、Rivest、Tarjan提出,最坏时间复杂度为O(n)
具体步骤:
1、将n个数分成n/5个组,每组5个元素,剩余的元素舍弃,对这五个元素进行排序(数量较少一般采用插入排序)
2、对每组的5个数求中位数,然后求中位数的中位数,实现起来通常将每组的中位数与前面的n/5个数交换,这样中位数又是连续的了,可以递归继续求中位数,直到最后求得一个数,记住这个数及其位置
3、运用快速排序的思想以这个中位数作为主元(pivot),进行快速交换排序,这样此数组刚好被这个主元分成两部分,一部分大于这个主元,一部分小于这个主元,记住主元的位置pos。
4、如果这个pos == k,则直接返回pos对应的值
如果pos<k,则 返回到第1步,递归调用,只用算数组left~pos-1的部分
如果pos>k,则 返回到第1步,递归调用,只用算数组pos+1~right的部分
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
*
*
7 2
2 3 4 7 5 9 1
*
*/
public class TopK_BFPRT {
static int N;
static int K;
static int[] array;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
String[] sa = line.split(" ");
N = Integer.valueOf(sa[0]);
K = Integer.valueOf(sa[1]);
line = br.readLine();
sa = line.split(" ");
array = new int[N];
for(int i=0; i<N; i++)
array[i] = Integer.valueOf(sa[i]);
System.out.println(bfprt(0, N-1, K));
}
/**
* bfprt中位数的中位数算法
* @param left
* @param right
* @param k
* @return
*/
private static int bfprt(int left, int right, int k){
int midPos = getMidPos(left, right);
int value = partition(left, right, midPos)+1;
if(value == k){
return array[k-1];
}else if(value > k){
return bfprt(left, value-2, k);
}else{
return bfprt(value, right, k);
}
}
/**
* 快速交换
* @param left
* @param right
* @param midpos
* @return
*/
private static int partition(int left, int right, int midpos){
int pivot = array[midpos];
swap(midpos, left);
int l = left;
int r = right;
while(l<r){
if(array[l] < array[r]){
r--;
}else{
swap(l, r);
if(pivot == array[l])
r--;
else
l++;
}
}
return l;
}
/***
* 求中位数的中位数
* @param left
* @param right
* @return
*/
private static int getMidPos(int left, int right){
if(right - left <= 5)
return insertSort(left, right);
int bleft = left;
for(int i=left; i+4<=right; i+=5){
int midv = insertSort(i, i+4);
swap(bleft++, midv);
}
return getMidPos(left, bleft);
}
/**
* 插入排序
* @param left
* @param right
* @return
*/
private static int insertSort(int left, int right){
for(int i = left + 1; i<=right; i++){
for(int j = i; j > left; j--){
if(array[j] < array[j-1]){
swap(j, j-1);
}
}
}
return (left+right)/2;
}
/**
* 数据交换
* @param pos1
* @param pos2
*/
private static void swap(int pos1, int pos2){
int temp = array[pos1];
array[pos1] = array[pos2];
array[pos2] = temp;
}
}
时间复杂度分析:
求中位数的中位数,首先用到了插入排序,插入排序5个数的时间复杂度为10,分成n/5组,约为O(2n+2n/5+2n/25+.....+1)约等于O(2.5n)
交换快速排序:O(n)
一次递归时间为O(2.5n)+O(n) = O(3.5n)然后二分求值O(3.5n)+O(3.5n/2)+O(3.5n/4)+......+1 约等于O(5n)由于5是常数所有时间复杂度为O(n).