概念
- 二叉堆:如果一棵完全二叉树各节点的键值与一个数组的元素具备一一对应的对应关系,那么这个完全二叉树就是二叉堆。
- 堆用数组存储键值。设结点为x,则父结点下标为floor(x >> 1),左子节点为 2 * x, 右子节点为2 * x + 1.
- 最大堆:父结点的键值大于等于左右子节点的键值。
- 最小堆:父结点的键值小于等于左右子节点的键值。
基本操作
参考博客:JVxie的个人博客之基本数据结构――堆的基本概念及其操作(这个人写的好棒啊!!)
最大堆(1-H)
- 让数值较大的上浮:使结点和父结点不断比较,如果结点比父结点大,就交换位置
void shift_up(int x)
{
while ((x >> 1) >= 1)
{
if (heap[x] > heap[x >> 1])
{
swap(heap[x], heap[x >> 1]);
x = x >> 1;
}
else
break;
}
}
- 让数值较小的下浮:在左右子节点选择大的那个和结点比较,如果结点比左右子节点小,就交换位置
void shift_down(int x, int H)
{
while (x * 2 <= H)
{
t = x * 2;
if (t + 1 <= H && heap[t + 1] > heap[t])
t++;
if (heap[t] > heap[x])
{
swap(heap[t], heap[x]);
x = t;
}
else
break;
}
}
- 插入结点:先插入到最后一个,然后进行上浮
void push(int x)
{
++H;
heap[H] = x;
shift_up(H);
}
- 弹出顶节点:先让顶结点和最后一个元素交换,然后去掉最后一个元素(即原来的顶结点),最后将此时的顶节点下沉。
void pop(int x)
{
swap(heap[1], heap[H]);
--H;
shift_down(1);
}
- 堆排序的数组:新开数组,然后每次将最大的赋值给新数组
void Heap_sort(a[])
{
num = 0;
while (num <= H)
{
++num;
a[num] = top();
pop();
}
}
- 建立大顶堆:
buildMaxHeap()
for i = H / 2 downto 1:
shift_down(i)
- H/2是最大的非叶子结点,自底向上进行下浮。
- 复杂度:对高度为1的H/2个结点执行(每个最多1次交换),对高度为2的H/4个结点执行下浮(每个最多2次交换)…最后对高度为log2(H)的1个结点执行(以下log表示以2为底)
S n = 1 × H 2 + 2 × H 2 2 + 3 × H 2 3 + . . . + l o g H × H H S_n = 1 \times \frac{H}{2} + 2 \times\frac{H}{2^2} + 3 \times\frac{H}{2^3}+...+logH \times\frac{H}{H} Sn=1×2H+2×22H+3×23H+...+logH×HH
设k = logH
S n = H × ( 1 2 + 2 2 2 + 3 2 3 + . . . k 2 k ) S_n=H\times(\frac{1}{2} + \frac{2}{2^2} + \frac{3}{2^3} +... \frac{k}{2^k}) Sn=H×(21+222+233+...2kk)
1 2 S n = H × ( 1 2 2 + 2 2 3 + . . . + k 2 k + 3 2 3 + . . . k 2 k + k 2 k + 1 ) \frac{1}{2} S_n=H\times(\frac{1}{2^2} + \frac{2}{2^3} +... +\frac{k}{2^k} + \frac{3}{2^3} +... \frac{k}{2^k} +\frac{k}{2^{k+1}}) 21Sn=H×(221+232+...+2kk+233+...2kk+2k+1k)
1式 - 2式:然后 用等比数列公式得出:
. . . ... ...
S n = H × ( 2 − 2 + k 2 k ) S_n = H \times (2 - \frac{2+k}{2^k}) Sn=H×(2−2k2+k)
所以最后复杂度为 O ( H ) = H × ( 2 − 2 + l o g H H ) O(H) = H \times (2 - \frac{2+logH}{H}) O(H)=H×(2−H2+logH).就是 O ( H ) O(H) O(H)的。
STL的heap相关操作
背景
- 底层容器:vector
- 头文件:
函数
示例代码
int main()
{
vector <int> a;
for i from 0 to n
{
cin >> num;
a.push_back(num);
}
make_heap(a.begin(), a.end(), cmp);
a.push_back(num)
push_heap(a.begin(), a.end(), cmp);
a[0] 为顶端最大值
pop_heap(a.begin(), a.end(), cmp); //将最大值放到vector最后一个位置
a.pop_back();
}
Priority Queue
背景
- 底层算法:用堆实现
- 队首元素是当前队列中优先级最高的那个。
- 参考网址:pzhu_cg_csdn
定义
priority_queue<int> q;
priority_queue<int, vector<int>,less<int> > q; (队首元素最大)
priority_queue<int, vector<int>,greater<int> > q;(队首元素最小)
示例代码
priority_queue<int> q;
for (int i = 0; i < n; i++)
q.push(num);
while (!q.empty())
cout << q.top() << endl;
q.pop();
问题
- 感觉堆的问题都可以用priority_queue代替啊……
例题1
- 题目链接:codevs 1063 合并果子
- 题目大意:有n堆果子,每次合并2堆果子时花费的体力为2堆果子质量和。求最后把n堆果子堆成一堆所需要花费的最小体力值。
- 思路:每次都选择值最小的两堆合并即可。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
priority_queue<int, vector<int>, greater<int> > q;
int x;
for (int i = 0; i < n; i++)
{
scanf("%d", &x);
q.push(x);
}
int ans = 0;
while (q.size() > 1)
{
int Min = q.top();
q.pop();
int secMin = q.top();
q.pop();
int sum = Min + secMin;
ans += sum;
q.push(sum);
}
printf("%d\n", ans);
}
return 0;
}
例题2
- 题目来源:《剑指offer》数据流中的中位数(牛客网)
- 题目大意: 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
题目思路
- 把数据平均分成2堆,右边堆的所有值都比左边的大。左边一堆为大顶推,很明显这个堆的顶部的值就是这个堆最大的值(即左边排序后数组最右的值),然后右边一堆为小顶堆,这个堆的顶部的值就是这个堆最小的值(即右边排序后数组最左的值)。这样就可以求中位数了。如果数组个数是偶数个,结果就是两个顶端值的平均值,如果数据个数是奇数个,那么结果就是小顶堆(这个取决于你把第一个值放在哪里,第一个值放哪里就取哪个堆的顶部值)的顶部值。
- 规定:将现有个数为偶数(即将放第奇数个值时)插入小顶堆。反之插入大顶堆。
- 这里有个问题:即如何在插入时保证右边堆的值永远大于左边。
- 插入第x个数(x为奇数)v理应插入小顶堆,小顶堆的值永远要大于大顶堆,而假设此时v 比大顶堆的顶部值小了。那么我们就不能把这个值插入小顶堆。
- 那就先插入大顶堆,让v和大顶堆的最大值交换,然后将大顶堆最大的值从大顶堆去掉加入到小顶堆中。
class Solution {
public:
priority_queue <int, vector<int>, greater<int> > smallq; //小顶堆
priority_queue <int> bigq; //大顶堆
void Insert(int num)
{
int size = smallq.size() + bigq.size();
if ((size & 1) == 0) //插入小顶堆
{
if (bigq.size() > 0 && num < bigq.top())
{
bigq.push(num);
num = bigq.top(); //将num替换成左边最大的数
bigq.pop();
}
smallq.push(num);
}
else //插入大顶堆
{
if (smallq.size() > 0 && num > smallq.top())
{
smallq.push(num);
num = smallq.top();
smallq.pop();
}
bigq.push(num);
}
}
double GetMedian()
{
int size = smallq.size() + bigq.size();
if (size == 0)
return 0;
double m = 0;
if ((size & 1) == 0)
m = (smallq.top() + bigq.top() + 0.0) / 2;
else
m = smallq.top();
return m;
}
};