寻找最大的K个数之堆排序(1)
一.问题:假设有一个含有n个不同元素的数组S,现在要求寻找出数组S中前K大的元素。
利用堆排序算法解决这个问题,有两个思路:
1.利用大根堆排序“每一趟都能产生1个最大值”的特性,来做K趟堆排序,则可找出这个前K大的元素
2.首先取出数组S中的前K个元素,利用这K个元素,建立小根堆,然后利用小根堆产生的最小值与剩余的S-K个元素相比较,如果后者大,则交换他们的位置,否则不变,这样也能产生前K大的元素。
二.分别实现这两种思路
回忆一下堆排序:1.自底向上修复原始堆(建堆) 2.取堆中最大的叶子取带堆的根元素,并输出根元素 3.自顶向下的修复根堆
其中最重要的子方法:以某个节点开始进行堆修复,以大根堆为例,大根堆具备这样的性质:根节点一定比其孩子结点大。
//大根堆修复(递归算法):1 void max_heapfy(vector<int> &vec,int i) // 时间复杂度为O(h),其中h为以i为根的子树的高度2 {
3 int largest;//getLeft(i):获得左孩子4 if(getLeft(i)<vec.size()&&vec[getLeft(i)]>vec[i])
5 largest = getLeft(i);
6 else
7 largest = i;//getRight(i):获得右孩子9 if(getRight(i)<vec.size()&&vec[getRight(i)]>vec[largest])
10 largest = getRight(i);
11 if(largest!=i)
12 {
13 swap(vec,i,largest); //交换数组中的vec[i]和vec[largest]
14 max_heapfy(vec,largest); //修复可能遭到破环的子树结构
15 }
16 }
//初始建堆的过程1 void build_heap(vector<int> &vec)
2 {
3 for(int start = vec.size()/2-1;start!=-1;start--)
4 max_heapfy(vec,start);
5 }
//主函数1 void main()
2 {初始化vec...
3 build_heap(vec); //ln(n)
4 while(vec.size()!=1) //n次循环
5 {
6 cout<<vec[0]<<endl;
7 swap(vec,0,vec.size()-1);
8 vec.pop_back();
9 max_heapfy(vec,0); //ln(n)
10 }
11 }
易知时间复杂度:ln(n)+n[ln(n)] 空间复杂度:o[n]
下面我们来改进算法,使之满足我们的要求
思路一:我们只需要在主程序里将循环的次数从N变为K即可,时间复杂度:ln(n)+K[ln(n)],不再罗列代码,很明显,初始建堆的过程显得很冗余。
思路二:
//修改后的大根堆修复:
1 void max_heapfy(vector<int> &vec,int i,int k) // O(h)
2 {
3 int largest;
4 if(getLeft(i)<k&&vec[getLeft(i)]>vec[i])
5 largest = getLeft(i);
6 else
7 largest = i;
8
9 if(getRight(i)<k&&vec[getRight(i)]>vec[largest])
10 largest = getRight(i);
11 if(largest!=i)
12 {
13 swap(vec,i,largest);
14 max_heapfy(vec,largest);
15 }
16 }
17
//修改初始建堆过程:1 void build_heap_k(vector<int> &vec,int k)
2 {
3 if(k<=vec.size())
4 return;
5 for(int start=k/2-1;i!=-1;k--)
6 max_heapfy(vec,start,k);
7 }
//更改主程序:1 void main()
2 {
3 build_heap_k(vec,k);
4 for(int i=k+1;k<vec.size()-k;k++)
5 {
6 if(vec[k+1]<vec[0])
7 swap(vec,0,k+1);
8 max_heapfy(vec,0,k);
9 }
10 }
时间复杂度:ln(k)+(n-k)ln(k) 明显的比思路的一的方法快,但是思路一的方法,给出的前K个数是按照顺序排列的