1 概念
最大堆:堆顶的key是堆里所有关键字中最大者;
最小堆:堆顶的key是堆里所有关键字中最小者;
堆是一棵完全二叉树,树中结点的值总是不大于(即最小堆)或者不小于(即最大堆)其孩子结点的值,所以每一个结点的子树也是一个堆。
根据最大堆和最小堆的思想,我们可以实现标准库容器:优先队列。
2 中位数可使用的堆思想
中位数:若奇数,则是中间位置的数字;若偶数,则是中间两个数字的平均值。
因此,整个数字可以分为两部分:一部分数据(小值堆)均比另一部分数据(大值堆)小。因此,将小值堆设置为最大堆,大值堆设置为最小堆,将数字平均分配到两个堆中,获取两个堆中的最值即可实现。
3 实现方案
每获得一个数据,先插入到最大堆中,然后将最大堆中的最大值插入到最小堆,就保证了最小堆中的所有数字大于最大堆的所有数字。
4 代码
常用的两种仿函数:升序greater<T>()
和降序less<T>()
,
push_heap(_Iter,_Iter,_Compare)
:其中_Compare
:对应上面的两种仿函数,其中greater是指树越向下越大,即最小堆,同理另一种是大顶堆。
常用操作:对于vector而言,首先数组push_back
插入元素,然后再调用push_heap
,它会使最后一个元素插到合适位置;而pop_heap
之后pop_back
则会将堆顶元素(即容器的第一个位置)和容器的最后一个位置交换位置,随后删除这个原来堆顶那个最值元素。
class Solution {
public:
void Insert(int num)
{
if( (min.size() + max.size()) % 2 != 0)
{
if(max.size() > 0 && num < max[0])//先在最大堆(小值堆)里面找到最大值
{
max.push_back(num);
push_heap(max.begin(),max.end(),less<int>());
num = max[0];//得到最大值
pop_heap(max.begin(),max.end(),less<int>());
max.pop_back();//去除最大值
}
min.push_back(num);//将最小堆(大值堆)里面填充最大值
push_heap(min.begin(),min.end(),greater<int>());
}
else
{
if(min.size() > 0 && num > min[0])
{
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
num = min[0];
pop_heap(min.begin(),min.end(),greater<int>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(),max.end(),less<int>());
}
}
double GetMedian()
{
//if(min.size() + max.size() == 0)
// return 0;
if((min.size() + max.size()) % 2 == 0)
return min[0];
else
return (min[0] + max[0]) / 2;
}
private:
vector<int> min;
vector<int> max;
};
在C++的STL种还有优先队列这个适配器,也可以如下操作:
class Solution {
private:
priority_queue<int, vector<int>, less<int> > p;
priority_queue<int, vector<int>, greater<int> > q;
public:
void Insert(int num)
{
if(p.empty() || num <= p.top()) p.push(num);
else q.push(num);
if(p.size() == q.size() + 2) q.push(p.top()), p.pop();
if(p.size() + 1 == q.size()) p.push(q.top()), q.pop();
}
double GetMedian()
{
return p.size() == q.size() ? (p.top() + q.top()) / 2.0 : p.top();
}
};