算法:
1 排序后取前K个算法,比较笨
2排序算法中有些通过取本次循环最小的K实现排序的,因此排下前K个元素就可以实现取得最小的K个元素,比如selectionSort、bubbleSort、heapSort,对于selectionSort和bubbleSort,复杂度为O(Kn)。
对于堆排序来说,有一些自己的实现变体:
a)最简单的就是O(n)的复杂度实现最小堆,然后排序最小的K个元素,复杂度为O(n+Klogn)
b) 将前面的K个元素生成最大堆,复杂度为0(K),然后剩下的n-K个元素和最大堆堆首比较,比堆首小就互换,堆自身再更新一下重新生成堆,复杂度为(n-K)logK,加起来复杂度为O(nlogK)。最后得到的前K个元素就是所需的前K个元素
注:a)算法和b)算法的复杂度到底哪个高,可以相除n取无穷得到极限的方法,大于1就是分母高,小于就是分子高,等于就是一样(高数的知识)。
结果是a)比较好,但实际上在1000w数量级的时候。两者差15ms,因此实际相差不大。原pdf中所说空间复杂度不为1无法理解,数组用自己下标本身也可以构建堆,因此空间就是O(1)的复杂度。
c) a)的变体,实际上堆就是成成递推变大或者变小的数据结构,由于只要取K个元素,因此其实不必维护整个堆,取了第一个元素剩下还要取K-1个,因此只要保证K-1层是堆即可(下面的元素除了堆首沉底的还是都比上层大),也就是说调整min(logn,K-1)次(原pdf说要调整K次,应该是多调了一次)。依次类堆。当n相对于k较大的时候,复杂度为生成堆的O(n)+每次取值调整的O(K×K)。
这几个算法其实都很类似,启发点就是数组在某些情况下看成堆,可以给问题的解决带来便利
3 我的第一反应就是快速排序的变体,pdf推导了很多东西,随机选择pivot得到平均复杂度为0(n),头中尾选中值绝大多数情况下,复杂度就是0(n),还有中位数的中位数,有个神奇的名字:BFPRT算法。。看的晕乎乎的。。这个算法复杂度应该是最小的。
但实际上对于太多的数据量,由于不可能把所有数据都塞入内存,因此,选取要K个数构成最大堆塞入内存,然后用剩下的大量数据去用算法2 b)实现结果。
3的代码在各种排序算法(4)里有了,下面的是2c),破坏部分堆,有点意思。。。
代码如下(生成堆以后每次调整都是堆顶和下面的互换。。。。。。写过的都写了这么久。。。。):
//默认k<a.length&&k>0,不然没什么意义
public void getMinKth(int[] a,int k){
createHeap(a);
for(int i=0;i<k;i++){
int temp=a[0];a[0]=a[a.length-1-i];a[a.length-1-i]=temp;//每次堆顶互换
int value1 = (int)Math.pow(2, k-i)-1-1; //沉k-i次的最大下标
int value2 = a.length-1-(i+1);
int end = value1 > value2? value2 : value1 ;
adjustment(a,0,end);
}
}
private void createHeap(int[] a){
for(int i=(a.length-1-1)/2;i>=0;i--){
adjustment(a,i,a.length-1);
}
}
/**
*
* @param a 需要调整位置的数组
* @param start 要调整元素的初始位置
* @param end 要调整堆位置的最后端点
*/
private void adjustment(int[] a,int start,int end){
while(2*start+1<=end){//存在左子树
if(start*2+2<=end&&a[start*2+2]<=a[start*2+1]){//存在右子树并且右子树小于左子树
if(a[start]>a[start*2+2]){//跟大于右子树
int temp=a[start];a[start]=a[start*2+2];a[start*2+2]=temp;
start = start*2+2;
}else{
break;
}
}else{//要替换的是左子树
if(a[start]>a[start*2+1]){
int temp=a[start];a[start]=a[start*2+1];a[start*2+1]=temp;
start = start*2+1;
}else{
break;
}
}
}
}
原文链接:http://blog.csdn.net/v_july_v/article/details/6278484