- 问题描述
给定一个无序数组,找到其中第k大的数。
- 解决方案
方案1:堆解法
维护一个包含k个数的堆,时间复杂度O(nlogk)
方案2:快排变形
方案3:BFPRT算法
本质上是快排变性算法的优化,每次在partion选取主元时取中位数,而快排主元是随机的。
堆解法
public static int kthNumberHeapSolution(int[] arr,int k){
//将前k个元素调整为最小堆
for(int i = 1;i < k;i ++){
int cur = i;
while(cur > 0 && arr[cur] < arr[(cur - 1) / 2]){
int temp = arr[cur];
arr[cur] = arr[(cur - 1)/2];
arr[(cur - 1) / 2] = temp;
cur = (cur - 1) / 2;
}
}
for(int i = k;i < arr.length;i ++){
if(arr[i] > arr[0]){
int t = arr[0];
arr[0] = arr[i];
arr[i] = t;
//调整堆
int cur = 0;
while(cur * 2 + 1 < k){
if(cur * 2 + 2 < k && arr[cur] > arr[cur * 2 + 2] &&
arr[cur * 2 + 1] > arr[cur * 2 + 2]){
int temp = arr[cur];
arr[cur] = arr[cur * 2 + 2];
arr[cur * 2 + 2] = temp;
cur = cur * 2 + 2;
}
else if(arr[cur] > arr[cur * 2 + 1]){
int temp = arr[cur];
arr[cur] = arr[cur * 2 + 1];
arr[cur * 2 + 1] = temp;
cur = cur * 2 + 1;
}
else
break;
}
}
}
return arr[0];
}
快排变形
public static int kthNumberQuickSortSolution(int[] arr,int k){
return kthNumberQuickSortSolutionHelp(arr,0,arr.length - 1,k);
}
public static int kthNumberQuickSortSolutionHelp(int[] arr,int l,int r,int k){
int t = l + (int)((r - l) * Math.random());
int[] partions = partion(arr,l,r,t);
int max = r - partions[0] + 1;
int min = r - partions[1] + 1;
if(max >= k && min <= k)
return arr[partions[0]];
else if (max < k){
return kthNumberQuickSortSolutionHelp(arr,l,partions[0] - 1,k - max);
}
else{
return kthNumberQuickSortSolutionHelp(arr,partions[1] + 1,r,k );
}
}
public static int[] partion(int[] arr,int l,int r,int k){
int temp = arr[k];
arr[k] = arr[l];
arr[l] = temp;
int less = l ;
int more = r;
for(int i = l + 1;i <= more;i ++){
if(arr[i] < temp){
int t = arr[i];
arr[i] = arr[less];
arr[less] = t;
less ++;
}
else if(arr[i] > temp){
int t = arr[i];
arr[i] = arr[more];
arr[more] = t;
more --;
i --;
}
}
return new int[]{less,more};
}
BFPRT解法
public static int BFPRT(int[] arr,int k){
return kthNumberBFPRT(arr,0,arr.length - 1,k);
}
public static int kthNumberBFPRT(int[] arr,int l,int r,int k){
int mid = findMid(arr,l,r);
int[] partions = partion(arr,l,r,mid);
int max = r - partions[0] + 1;
int min = r - partions[1] + 1;
if(max >= k && min <= k)
return arr[partions[0]];
else if (max < k){
return kthNumberBFPRT(arr,l,partions[0] - 1,k - max);
}
else{
return kthNumberBFPRT(arr,partions[1] + 1,r,k );
}
}
public static int findMid(int[] arr,int l,int r){
if(r - l < 4){
for(int i = l + 1;i <= r;i ++){
for(int j = i;j > l;j --){
if(arr[j] < arr[j - 1]){
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
else break;
}
}
return l + ((r - l ) >> 1);
}
for(int i = l;i + 4 <= r;i += 5){
for(int j = l + 1 ;j < l + 5;j ++){
for(int k = j;k >= l + 1;k --){
if (arr[k] < arr[k - 1]){
int temp = arr[k];
arr[k] = arr[k - 1];
arr[k - 1] = temp;
}
else
break;
}
}
}
int index = l;
for(int i = l;i + 4 <= r;i += 5){
int temp = arr[i + 2];
arr[i + 2] = arr[index];
arr[index] = temp;
index ++;
}
return findMid(arr,l,index - 1);
}
算法测试
public static void main(String[] args) {
int len = 1000000;
int k = 1000;
int[] arr = new int[len];
for(int i = 0;i < len;i ++)
arr[i] = i;
long start = System.nanoTime();
System.out.println(kthNumberHeapSolution(arr,k));
long end = System.nanoTime();
System.out.println("堆方法用时:" + (end- start) / 1000000 + "ms") ;
start = System.nanoTime();
System.out.println(kthNumberQuickSortSolution(arr,k));
end = System.nanoTime();
System.out.println("快排方法用时:" + (end- start) / 1000000 + "ms") ;
start = System.nanoTime();
System.out.println(BFPRT(arr,k));
end = System.nanoTime();
System.out.println("BFPRT方法用时:" + (end- start) / 1000000 + "ms") ;
}
通过对比发现,堆解法明显和k的大小有关系。而快排变形解法时间复杂度一般优于BFPRT解法,可能是因为BFPRT解法在找中位数时耗费了计算资源,因为理论上BFPRT解法时间复杂度为O(N)。