用堆在海量数据中找出最大或最小的k个数,效率非常高。
1、在一组数据中找出最小的k个数
解题思路:
要找出最小的k个数,我们可以先用这组数据中的k个数构建一棵“最大堆”,然后再将剩下的元素与堆顶元素相比。如果大于堆顶元素,则不做处理,继续向下比较。如果小于堆顶元素,则将堆顶元素与这个元素交换,然后再恢复堆序,继续向下比较。这样的话最后这个堆里面保存的就是最小的k个数。
例:在{10,16,18,12,11,13,15,17,14,19}里面找出最小的4个数
1、用前四个数构造一颗最大堆
(2、用剩下的数与堆顶元素进行比较,如图:
2、在一组堆中找出最大的前k个数
与找最小的数是相同的道理,不过找最大的数时要建立一个k个数的最小堆。
时间复杂度分析:
建立一个k个数的堆:O(k*lgk)
向后比较:O((N-k)*lgk)
时间复杂度为:O(N*lgK)
//代码
#pragma once
#include<vector>
#include<cassert>
using namespace std;
template<typename T>
struct SmallNum //求最小的数,建最大堆
{
bool operator()(const T& l,const T& r)
{
return l < r;
}
};
template<typename T>
struct GreatNum //求最大的数,建最小堆
{
bool operator()(const T& l,const T& r)
{
return l>r;
}
};
template<typename T,class Compare=SmallNum<T>> //默认求最小的k个数
class HeapSearch
{
public:
HeapSearch()
{}
HeapSearch(T* a, int size,int k)
{
assert(size>=k);
assert(k>0);
//建一个k个数的堆,如果求最小的k个数则建最大堆,反之建最小堆
_a.reserve(k);
for (int i = 0; i < k; ++i)
{
_a.push_back(a[i]);
}
for (int i = (k - 2) / 2; i >= 0; --i)
{
//向下调整算法
AdjustDown(i,k);
}
FindKNum(a,size,k);
}
void Display()
{
for (size_t i = 0; i < _a.size(); i++)
{
cout << _a[i] << " ";
}
cout << endl;
}
protected:
void AdjustDown(int root,int k)
{
assert(!_a.empty());
int parent= root; //最后一个非叶子结点
int child = parent * 2 + 1; //求左孩子结点的下标
while (child<k)
{
if ((child + 1) < k&&Compare()(_a[child],_a[child+1]))
child++;
if (Compare()(_a[parent], _a[child]))
{
swap(_a[parent],_a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void FindKNum(T*a ,int size,int k)
{
for (int i = k; i < size;i++)
{
if (Compare()(a[i], _a[0]))
{
_a[0] = a[i];
AdjustDown(0,k);
}
}
}
private:
vector<T> _a;
};