[数据结构与算法-01]二叉堆
二叉堆
性质
保证父节点优先级始终高于子节点。
思路
- 存储: 二叉树的存储方式为数组:
- 每个节点有一个编号
pos - 左子节点的编号为
2*pos(pos<<1) - 右子节点的编号为
2*pos+1((pos<<1)+1)
- 每个节点有一个编号
- 插入:
- 向堆底插入新节点
- 向上维护
- 判断是否到达根节点
- 到达根节点结束维护
- 未到达根节点继续下一步
- 判断父子节点的优先级
- 若父节点优先级更低则交换父节点和子节点并递归(从父节点开始向上维护)
- 若父节点优先级更高则结束维护
- 判断是否到达根节点
- 删除:
- 将要删除的节点和堆底的节点交换并删除
- 向下维护
- 这时要同时考虑两个子节点,分别有以下情况:
- 两个子节点均不可交换(无子节点或子节点优先级低于父节点),结束维护
- 只有一个子节点可交换(子节点编号在堆内且优先级高于父节点),交换父子节点并递归
- 两个子节点均可交换,将父节点和优先级更高的子节点交换并递归
- 这时要同时考虑两个子节点,分别有以下情况:
- 查询:直接返回根节点的值
- Tip:根节点下标从1开始
代码思路
#include <iostream>
#define MAX 1000000
using namespace std;
int heap[MAX + 10] = { 0 };
int n, cnt=0; // cnt为堆中元素个数;
short op;
// 自下而上维护堆
void buttomUp(int pos) {
// 到达根节点或者父节点优先级高于子节点,结束
if (pos == 1 || heap[pos / 2] <= heap[pos])return;
// 否则交换
else {
swap(heap[pos], heap[pos / 2]);
// 继续从父节点开始向上维护
buttomUp(pos / 2);
}
}
// 自上而下维护堆
void Updown(int pos) {
// 有两个子节点
if (2 * pos + 1 <= cnt) {
// 右节点的优先级高于左节点
if (heap[2 * pos + 1] < heap[2 * pos]) {
// 右节点优先级高于父节点,交换
if (heap[2 * pos + 1] < heap[pos]) {
swap(heap[pos], heap[2 * pos + 1]);
// 继续从右节点向下维护
Updown(2 * pos + 1);
}
// 右节点优先级小于父,返回
else return;
}
// 左节点的优先级高于右节点
else {
// 左节点优先级高于父节点,交换
if (heap[2 * pos] < heap[pos]) {
swap(heap[pos], heap[2 * pos]);
// 继续从左节点向下维护
Updown(2 * pos);
}
// 左节点优先级小于父节点,返回
else return;
}
}
// 只有左节点
else if (2 * pos <= cnt) {
// 左节点优先级高于父节点,交换
if (heap[2 * pos] < heap[pos]) {
swap(heap[pos], heap[2 * pos]);
// 继续从左节点向下维护
Updown(2 * pos);
}
// 左节点优先级小于父节点,返回
else return;
}
// 没有节点,结束
else {
return;
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> op;
if (op == 1) {
cnt++;
cin >> heap[cnt];
buttomUp(cnt);
}
else if (op == 2) {
cout << heap[1] << endl;
}
else if (op == 3) {
if (cnt >= 1) {
swap(heap[1], heap[cnt]);
heap[cnt] = 0;
cnt--;
Updown(1);
}
}
}
}
堆的操作
交换
void swap(int a, int b){
int c = heap[a];
heap[a] = heap[b];
heap[b] = c;
}
插入
void add(int k) {
cnt++;
heap[cnt] = k;
int cpos = cnt;
while (cpos > 1) {
if (heap[cpos] < heap[(cpos >> 1)]) {
swap(cpos, (cpos >> 1));
cpos = (cpos >> 1);
}
else return;
}
}
删除
void del() {
heap[1] = heap[cnt];
cnt--;
int cpos = 1;
while ((cpos << 1) <= cnt) {
if (heap[(cpos << 1) + 1] < heap[(cpos << 1)] && (cpos << 1) + 1 <= cnt) {
if (heap[cpos] > heap[(cpos << 1) + 1]) {
swap(cpos, (cpos << 1) + 1);
cpos = (cpos << 1) + 1;
}
else return;
}
else {
if (heap[cpos] > heap[(cpos << 1)]) {
swap(cpos, (cpos << 1));
cpos = (cpos << 1);
}
else return;
}
}
}
查询
void get(){
printf("%d", heap[1]);
}
STL
手写堆费时易错,可利用在C++的STL中的优先队列。
包含头文件
#include <queue>
创建优先队列
// 大根堆
priority_queue<int> q_name;
// 小根堆
priority_queue<int,vector<int>,greater<int> > q_name;
优先队列的操作
q.top() //取得堆顶元素,并不会弹出
q.pop() //弹出堆顶元素
q.push() //往堆里面插入一个元素
q.empty() //查询堆是否为空,为空则返回1否则返回0
q.size() //查询堆内元素数量
P3378 【模板】堆
题目描述
给定一个数列,初始为空,请支持下面三种操作:
- 给定一个整数 xx,请将 xx 加入到数列中。
- 输出数列中最小的数。
- 删除数列中最小的数(如果有多个数最小,只删除 11 个)。
输入格式
第一行是一个整数,表示操作的次数 nn。
接下来 nn 行,每行表示一次操作。每行首先有一个整数 opo**p 表示操作类型。
- 若 op = 1o**p=1,则后面有一个整数 xx,表示要将 xx 加入数列。
- 若 op = 2o**p=2,则表示要求输出数列中的最小数。
- 若 op = 3o**p=3,则表示删除数列中的最小数。如果有多个数最小,只删除 11 个。
输出格式
对于每个操作 22,输出一行一个整数表示答案。
输入输出样例
输入 #1复制
5 1 2 1 5 2 3 2输出 #1复制
2 5说明/提示
数据规模与约定
- 对于 30%30% 的数据,保证 n \leq 15n≤15。
- 对于 70%70% 的数据,保证 n \leq 10^4n≤104。
- 对于 100%100% 的数据,保证 1 \leq n \leq 10^61≤n≤106,1 \leq x \lt 2^{31}1≤x<231,op \in {1, 2, 3}o**p∈{1,2,3}。
完整解答
- 优化了IO,将递归改为循环,使用了位运算(537ms / 3.16MB / 1.08KB)
#include <cstdio>
#define MAX 1000000
int heap[MAX + 10] = { 0 };
int n, cnt=0;
int op;
void swap(int a, int b) {
int c;
c = heap[a];
heap[a] = heap[b];
heap[b] = c;
}
void add(int k) {
cnt++;
heap[cnt] = k;
int cpos = cnt;
while (cpos > 1) {
if (heap[cpos] < heap[(cpos >> 1)]) {
swap(cpos, (cpos >> 1));
cpos = (cpos >> 1);
}
else return;
}
}
void del() {
heap[1] = heap[cnt];
cnt--;
int cpos = 1;
while ((cpos << 1) <= cnt) {
if (heap[(cpos << 1) + 1] < heap[(cpos << 1)] && (cpos << 1) + 1 <= cnt) {
if (heap[cpos] > heap[(cpos << 1) + 1]) {
swap(cpos, (cpos << 1) + 1);
cpos = (cpos << 1) + 1;
}
else return;
}
else {
if (heap[cpos] > heap[(cpos << 1)]) {
swap(cpos, (cpos << 1));
cpos = (cpos << 1);
}
else return;
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &op);
if (op == 1) {
int k;
scanf("%d", &k);
add(k);
}
else if (op == 2) {
printf("%d\n", heap[1]);
}
else if (op == 3) {
del();
}
}
return 0;
}
- 利用STL( 2.14s / 2.64MB / 452B )
#include <iostream>
#include <queue>
using namespace std;
int n, op;
priority_queue<int,vector<int>,greater<int> > q;
int main() {
cin >> n;
for (int i = 1; i <= n; i++){
cin >> op;
if (op == 1){
int val;
cin >> val;
q.push(val);
}else if (op == 2){
cout << q.top() << endl;
}else if (op == 3){
q.pop();
}
}
return 0;
}

浙公网安备 33010602011771号