[数据结构与算法-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 【模板】堆

题目描述

给定一个数列,初始为空,请支持下面三种操作:

  1. 给定一个整数 xx,请将 xx 加入到数列中。
  2. 输出数列中最小的数。
  3. 删除数列中最小的数(如果有多个数最小,只删除 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}。

完整解答

  1. 优化了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;
}
  1. 利用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;
}
posted @ 2021-02-25 22:47  ChenHongKai  阅读(155)  评论(0)    收藏  举报
1 2 3
4