二叉堆的简单板子+理解+例题

首先,我们先要了解堆是什么?

堆:是一种高级树状数据结构,是一种完全二叉树。

(完全二叉树指的是,除了叶子节点,每个节点均有左右两个子节点的树状结构)

而,二叉堆是堆的最常见的实现方式。

二叉堆又可以分为:大根堆,小根堆。(可以用c++ 的 stl实现)
大根堆:每一个节点,大于等于其子节点。(从堆顶到堆底不严格递增)

小根堆:每一个节点,小于等于其子节点。(从堆顶到堆底不严格递减)

那么对于二叉堆,我们是需要手动去实现一些它的一些基本操作。

  1. 向下调整
  2. 向上调整
  3. 插入一个元素
  4. 求堆中最大值/最小值(堆顶)
  5. 删除堆中最大值/最小值

下面先实现最大堆/大根堆的操作:

1.使用vector容器实现:

//定义一个最大堆,先给里面装填一个空元素,使得后续插入的元素下标一一对应:
//意思是,第一个数的下标就是 1,而不是0 ,同时也是 size() -1 
vector<int > big(1);

2.向上调整

void upp(int pos) {
    while (pos > 1) {                        // 循环直到节点到达堆顶
        if (big[pos] > big[father]) {        // 如果当前节点的值大于其父节点的值
            swap(big[pos], big[father]);     // 交换当前节点与父节点的值
        }
        else break;                          // 如果不满足最大堆性质,终止循环
        pos = father;                        // 更新当前节点的位置为父节点
    }
}

3.向下调整

void down(int pos) {
    int size = big.size();                    // 获取堆的大小
    while (2*pos <= size-1) {                 // 当前节点有至少一个子节点时循环
        int son;
        if (rson <= size - 1 and big[lson] < big[rson]) {       // 如果当前节点有右子节点且右子节点的值大于左子节点的值
            son = rson;                                         // 则选取右子节点作为子节点
        }
        else son = lson;                                        // 否则选取左子节点作为子节点
        if (big[pos] < big[son])swap(big[son], big[pos]);        // 如果当前节点的值小于子节点的值,则交换它们的位置
        else break;                                             // 如果不满足最大堆性质,终止循环
        pos = son;                                              // 更新当前节点的位置为子节点
    }
}

4.插入一个数:

void insert(int val) {
    big.push_back(val);              // 将元素 val 添加到堆的末尾

    upp(big.size() - 1);             // 调用 upp 函数,以维护最大堆性质
}

5.删除最大值:

void earse_big() {
    if (big.size() > 1) {                   // 如果堆中有至少两个元素
        big[1] = big[big.size() - 1];       // 将第一个元素用最后一个元素覆盖
        big.pop_back();                     // 删除最后一个元素
        down(1);                            // 对堆顶元素进行向下调整,以满足最大堆性质
    }
}

6.返回最大值:

int get_max() {
    if (big.size() > 1) {
        return big[1];
    }
}

以上就是二叉堆中的最大堆实现的过程。

不过在实际的写题中,我们不需要每次手写一个二叉堆。

可以直接用现成的stl 容器,priority_queue;

下面简单介绍以下stl的用法:

priority_queue<int>  bigheap;  //priority_queue 默认大根堆
priority_queue<int, vector<int>, greater<int> > littleheap; // 如果要定义小根堆,就要写全参数
// priority_queue 的参数为: 数据类型、容器类型、定义类型。 
//如果是小根堆, 我们在第三个参数那里改成: greater<int> 
//如果是大根堆:完整的写法就是: priority_queue<int , vector<int> , less<int> >  堆的名字

然后,下面是一些堆的函数:

priority_queue<int>  big;
priority_queue<int, vector<int>, greater<int> > little;

int main() {

	big.push(1); //插入的同时自动调整位置
	big.pop();//删除堆顶元素
	big.top()//返回堆顶元素 最大值/最小值

}

下面给一道堆的模板题:

P3378 【模板】堆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3378答案:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
#include<list>
using namespace std;




#define ll long long
#define lson pos<<1
#define rson (pos<<1)|1
#define father pos>>1
const int N = 1e6 + 7;
//二叉堆:
// 最大堆,最小堆
// 最大堆要满足一个性质:任意一个节点,如果它的子节点存在的话,这个节点的值是要大于等于它的子节点的任意的值
// 
// 1、向下调整的函数
// 2、向上调整的函数
// 3、向一堆数据中插入一个元素
// 4、在一堆数据中删除一个元素(最大值)
// 5、求出一堆数据里面的最大值。
//
//
vector<int > heap(1);
void heapup(int pos) { //node 指的是vector下标


	while (pos > 1) {

		if (heap[pos] < heap[father]) {
			swap(heap[pos], heap[father]);
		}
		else {
			break;
		}
		pos = father;
	}


}

//第一个问题: 为什么heapdown函数中 循环的条件要取等
void heapdown(int pos) {


	int size = heap.size(); //实际上堆里面的元素为 size-1, size指的是一个空的下标
	

	while ( lson < size) {
		int son;


		if (rson<size and heap[lson] > heap[rson]) {
			son = rson;
		}
		else son = lson;


		if (heap[pos] < heap[son])break;

		else {
			swap(heap[pos], heap[son]);
		}


		pos = son;
	}
}


void insert(int val) {
	

	heap.push_back(val);
	int size = heap.size();
	heapup(size - 1);


}


int get_min() {


	return heap[1];


}


void earse_min() {

	if (heap.size() > 1) {

		heap[1] = heap[heap.size() - 1];
		heap.pop_back();
		heapdown(1);

	}

}


bool empty() {


	if (heap.size() > 1)return true;
	else return false;
}

int main() {
	
	int n;
	cin >> n;
	while (n--) {
		int op;
		cin >> op;
		if (op == 1) {
			int x;
			cin >> x;
			insert(x);
		}
		else if (op == 2) {
			cout<<get_min();
			cout << '\n';
		}
		else {
			earse_min();
		}
	}
}

下面讲一下,二叉堆的综合运用:

1. 对顶堆

先给一个模板题,来看看对顶堆的使用场景

P1801 黑匣子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1801然后,来介绍一下对顶堆:

堆顶堆由两个堆组成,一个大根堆,一个小根堆。

比如一遍往堆里插入元素,一遍问第i大的元素是哪个?

我们可以这样写:

它问第i大的元素是哪个?

我们就可以构造出一个这样的形状

 不断往小根堆中插入元素,直到插满i个元素,此后的话,执行这样一个操作:

先往小根堆里插入元素,然后取出小根堆的堆顶,加入上面的大根堆,然后删除小根堆的堆顶。

这样就实现了一个目的:

小根堆内的元素仍然是i个,但在新元素插入后,调整了大小关系,仍然使得小根堆的堆顶的元素是当前的第i大的元素(即时现在有超过i个元素,大于第i大的元素,都被放到了大根堆里)

如果i开始变化,如i变成i+1,那么我们直接把当前大根堆的堆顶的元素加入小根堆中,这个元素一定会在小根堆的堆顶,然后我们在删除大根堆的堆顶,使之调整结构

那么对于求第i小的元素,我们也是同样的道理,只要下面放大根堆,上面放小根堆,维护大根堆内的元素为i个,那么大根堆的堆顶就是在当前两个堆中,第i小的那个元素:

上题解:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
#include<list>
using namespace std;




#define ll long long
#define lson pos<<1
#define rson (pos<<1)|1
#define father pos>>1
const int N = 2e6 + 7;
priority_queue<int>  bigheap;
priority_queue<int, vector<int>, greater<int> > littleheap;


int a[N];//元素
int opt[N];//操作
int main() {
	int m, n; //元素个数,操作个数
	cin >> m >> n;
	for (int i = 1; i <= m; i++) {
		cin >> a[i];
	}
	for (int j = 1; j <= n; j++) {
		cin >> opt[j];
	}

	int tot = 1, j = 1;

	for (int i = 1; i <= m; i++) {

		bigheap.push(a[i]);

		if (bigheap.size() >= tot) {
			littleheap.push(bigheap.top());
			bigheap.pop();
		}
		

		while (i == opt[j]) {

			cout << littleheap.top() << endl;

			
			bigheap.push(littleheap.top());
			
			
			littleheap.pop();
			j++;
			tot++;
		}
	}
}

 

有一天在写洛谷的一道题的时候,我想出来大概思路,但是有几步我想破头也无法实现。

后来看了题解,发现原来结构体可以这样使用。

比如,现在有一个结构体: 

struct person {
	char gender;
	int age, high, height;
};

它表示的是一个人的一些信息。

然后又给你一个vector容器,里面装的是person类型的元素

vector<person> a;

嗯。。如果我们要将gender,age,high,height都导入进容器里面应该怎么做?

vector<person> a;

int main() {

	a.push_back(person{ 'F', 18, 180, 150});
	person t = a[0];
	cout << t.age;
}

只需要按照这个格式:

 push_back( 结构体名字{  // 按照成员变量定义的顺序写你想要赋的值// } )

切记,一定要按照顺序结构体里的顺序赋值。

好的,现在我们给出那道题目:           

P1878 舞蹈课 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1878


简述一下,我第一次想的思路是将 (异性 and 相邻的差值 )插入最小堆里面
因为,题目要求,如果差值相等,输出最左边的一对,所以我们需要记录每个人站的编号。

然后又因为,如果出列了一对,那么这一对的左右两边的人要自动补齐,所以这样就又多了一对的数据,把这一对数据插入最小堆即可。 这一对的数据很好得到,就是出列的那一对的左边和右边,我们应该怎么把它们得到?所以我当时想到了链表。

嗯。。。不过我足足调试了两小时,因为初步的内容很粗糙,细节不够到位。

  • 编号为0和编号为n+1的点怎么办?
  • 怎么实现如果两数相等让左边的先出列
  • 出列的终止条件是什么

最后我想了一个很绝的方法来杜绝0,n+1,直接给0,n+1性别赋值z

然后如果 前面的后面的加起来 == N+B 或者相减== N-B

再加上本地ide背锅(明明最后的答案是对的,但是编辑器不给过,就浪费了好久时间,后面无奈交了,发现对了。。无语)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
#include<list>
using namespace std;




#define ll long long
#define lson pos<<1
#define rson (pos<<1)|1
#define father pos>>1
const int N = 2e6 + 7;

struct person {
	char gender;
	int pos, pre, nxt, val; //一个人的位置,前驱,后继,舞蹈技术,性别
}a[N];

struct heap {
	int margin, left, right;//差值
};

bool operator < (heap a, heap b) {
	if (a.margin != b.margin) {
		return a.margin > b.margin;
	}
	return a.left > b.left;
}

priority_queue<  heap > dance;

bool flag[N];
char gender[N];
int skill[N];
int r[N], l[N];
int main() {


	int n;  cin >> n;
	for (int i = 1; i <= n; i++)cin >> gender[i];
	for (int i = 1; i <= n; i++)cin >> skill[i];
	//gender[n + 1] = 'q';
	//gender[0] = 'a';

	for (int i = 1; i <= n; i++) {

		a[i].pre = i - 1;
		a[i].nxt = i + 1;
		a[i].pos = i;
		a[i].val = skill[i];
		a[i].gender = gender[i];


		if (gender[i + 1] + gender[i] == 'B' + 'G' and i < n)
			dance.push(heap{ abs(skill[i] - skill[i + 1]), i, i + 1 });
	}

	int cnt = 0;
	while (!dance.empty()) {

		heap t = dance.top();
		dance.pop();

		if (flag[t.left] == 0 and flag[t.right] == 0) {


			cnt++;
			l[cnt] = t.left;
			r[cnt] = t.right;


			flag[t.left] = 1;
			flag[t.right] = 1;


			a[a[t.left].pre].nxt = a[t.right].nxt;
			a[a[t.right].nxt].pre = a[a[t.left].pre].pos;


			int pre = a[a[t.left].pre].pos;
			int nxt = a[a[t.right].nxt].pos;


			if ((int)(a[pre].gender + a[nxt].gender) == int('G' + 'B') and (flag[pre] == 0 and flag[nxt] == 0)) {

				int margin = abs(a[pre].val - a[nxt].val);

				dance.push(heap{ abs(margin), pre, nxt });

			}
		}




	}
	cout << cnt << endl;
	for (int i = 1; i <= cnt; i++) {
		cout << l[i] << ' ' << r[i] << endl;
	}
}

//初始化堆:将“异性之间的舞蹈技术差值的绝对值、左边的人是谁、右边的人是谁” 插入堆中.
// 令一个heap变量  t = 堆顶, 那么t的left,right 就是需要出列的人的位置
// 然后用链表把这两个人连接起来
// 然后我们需要判断  t.left 的前面一个人(t.left .pre) .gender
// 和 
// t.right 的后面一个人 (t.right.nex).gender
// 是不是异性。 
// 如果是异性的话,我们就把这个信息插入到堆里面。
// 
//
/*8
BGBGBGBG
1 1 1 8 7 1 7 1
*/




  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值