Algorithm11.2 数据结构 堆
[TOC]
数据结构 堆 & 手写堆
堆:
- 分类:最大堆、最小堆
- 过程:首先按照完全二叉树的形式,将序列填补到初始的堆中
(堆-附加:利用堆的性质来判断是否是堆:「PAT1147」,判断方法就是直接对给定的层次遍历的序列来判断,是否满足
a[i] > a[2<<i] && a[i] > a[2<<i|1]
或者a[i] < a[2<<i] && a[i] < a[2<<i|1]
,来查看是否是最大堆或者最小堆用到堆优化的思路不一定是用堆,也可能是通过利用优先队列进行优化【堆的优化的应用场景:解决重复排序问题,每添加一个新的元素【在线更新】,都要重新排一次序 => 可以用堆进行优化】
用到堆的方式:
- 手写堆:「例如下面的P3378手写堆 // P1631RMQ问题」的up/down方法来维护堆
- 直接利用优先队列来优化复杂度,优化的方式就是直接拿堆顶,进而取堆顶得到最大最小 或者 堆结构带来的排序「P2085取前最小」的每次拿堆顶,然后在线插入新的元素之后重新自动排序获取下一次的堆顶。
判断堆是否为最大堆、最小堆
https://pintia.cn/problem-sets/994805342720868352/problems/994805342821531648
1、判断是否是最大堆最小堆:直接判断父节点与两个子节点的的大小关系。
可以通过维护两个变量is_max = 1 / is_min = 1
,当存在有子节点小于父节点=>不满足最小堆了把is_min = 0
;当存在有子节点大于父节点=>不满足最小堆了把is_max = 0
最后看is_max
和is_min
两个变量,是否是最大最小堆。
// 从底部往上遍历
for (int j = 2; j <= m; ++j) {
if (a[j] > a[j/2]) is_big = 0;
else is_small = 0;
}
https://www.luogu.com.cn/problem/P3378
手写堆 实现 获取区间前几大/最大
1、数据结构:一个模拟堆的数组myheap
2、堆的基本操作:
- insert:插入新的元素
- myheap[1]:获取堆顶元素
- deletetop:删除堆顶元素
3、【下面范例都是小顶堆】
插入新的元素 => 从堆底往堆顶移动索引
while (idx > 1) {
if (myheap[idx] < myheap[idx>>1]) {
swap(myheap[idx], myheap[idx>>1]);
idx >>= 1;
}
}
删除堆顶元素 => 从堆顶网堆底移动索引
int son = idx<<1;
while (son <= size) {
if (son < size && myheap[son] > myheap[son+1]) son++;
if (myheap[son] < myheap[idx]) {
swap(myheap[son], myheap[idx]);
idx <<= 1;
}
}
完整代码:
#include <iostream>
using namespace std;
#define maxn 1000010
int t, myheap[maxn], size, tp, ta;
void up(int idx) {
while (idx > 1) {
if (myheap[idx] < myheap[idx>>1]) {
swap(myheap[idx], myheap[idx>>1]);
idx >>= 1;
} else break;
}
}
void insert(int x) {
myheap[++size] = x;
up(size);
}
void down(int idx) {
int son = idx<<1;
while (son <= size) {
if (son < size && myheap[son] > myheap[son+1]) son++;
if (myheap[son] < myheap[idx]) {
swap(myheap[son], myheap[idx]);
idx = son; son <<= 1;
} else break;
}
}
void deletetop() {
myheap[1] = myheap[size--];
down(1);
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d", &tp);
if (tp == 1) {
scanf("%d", &ta);
insert(ta);
} else if (tp == 2) printf("%d\n", myheap[1]);
else deletetop();
}
return 0;
}
用STL库中的堆
用优先队列模拟堆的最大
声明一个小顶堆即可。priority_queue<int, vector<int>, greater<int>> pq;
#include <iostream>
using namespace std;
#include <queue>
priority_queue<int, vector<int>, greater<int>> pq;
int m, op, t;
int main() {
scanf("%d", &m);
while (m--) {
scanf("%d", &op);
switch (op) {
case 1: scanf("%d", &t);pq.push(t);break;
case 2: printf("%d\n", pq.top());break;
case 3: pq.pop();break;
}
}
return 0;
}
限定长度的堆 => 类似于滑动窗口
(RMQ问题,也可用于解决类似于滑动窗口,单调队列)
https://www.luogu.com.cn/problem/P1631
1、注意:选取最小的几个的话,不能用最小堆,需要用最大堆,然后每次再来新的元素之后,判断此元素和堆顶的大小关系:如果大于堆顶=>直接忽略;小于堆顶,把堆顶替换,然后往下down即可。
#include <iostream>
using namespace std;
#define maxn 100010
int n, myheap[maxn], size, a[maxn], b[maxn], ans[maxn];
void up(int idx) {
while (idx > 1) {
if (myheap[idx] > myheap[idx>>1]) {
swap(myheap[idx], myheap[idx>>1]);
idx >>= 1;
} else break;
}
}
void insert(int x) {
myheap[++size] = x;
up(size);
}
void down(int idx) {
int son = idx<<1;
while (son <= size) {
if (son<size && myheap[son]<myheap[son+1]) son++;
if (myheap[idx] < myheap[son]) {
swap(myheap[idx], myheap[son]);
idx=son;son<<=1;
} else break;
}
}
void deletetop(){
myheap[1] = myheap[size--];
down(1);
}
void change(int x) {
myheap[1] = x;
down(1);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
for (int i = 1; i <= n; ++i) insert(a[1]+b[i]);
for (int i = 2; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (a[i] + b[j] > myheap[1]) break;
change(a[i]+b[j]);
}
for (int i = 1; i <= n; ++i) {
ans[i] = myheap[1];
deletetop();
}
for (int i = n; i >= 1; --i) printf("%d ", ans[i]);
return 0;
}
取前最小 - 复杂的题意
https://www.luogu.com.cn/problem/P2085
1、最小值可能是某一个函数一直往后取x,也有可能是不同的函数各自往后延伸,因此至于延伸哪个,每次都要排序,然后在线排序 => 堆型数据结构优化
2、用结构体来储存每个单元,然后在每次优先队列取top的,留下id和xx的值,然后往后拓展。
#include <iostream>
using namespace std;
#define maxn 10010
struct node {
int val, id, xx;
node(int val, int id, int xx): val(val), id(id), xx(xx){}
bool operator < (const node n) const {return val > n.val;}
};
int n, m, a[maxn], b[maxn], c[maxn];
#include <queue>
priority_queue<node, vector<node>, less<node>> pq;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d%d%d", &a[i], &b[i], &c[i]);
pq.push(node(a[i]+b[i]+c[i], i, 1));
}
for (int i = 1; i <= m; ++i) {
node nw = pq.top();pq.pop();
printf("%d ", nw.val);
int nid = nw.id, nxx = nw.xx+1;
pq.push(node(a[nid]*nxx*nxx+b[nid]*nxx+c[nid], nid, nxx));
}
return 0;
}