首先,我们先要了解堆是什么?
堆:是一种高级树状数据结构,是一种完全二叉树。
(完全二叉树指的是,除了叶子节点,每个节点均有左右两个子节点的树状结构)
而,二叉堆是堆的最常见的实现方式。
二叉堆又可以分为:大根堆,小根堆。(可以用c++ 的 stl实现)
大根堆:每一个节点,大于等于其子节点。(从堆顶到堆底不严格递增)小根堆:每一个节点,小于等于其子节点。(从堆顶到堆底不严格递减)
那么对于二叉堆,我们是需要手动去实现一些它的一些基本操作。
下面先实现最大堆/大根堆的操作:
1.使用vector容器实现:
//定义一个最大堆,先给里面装填一个空元素,使得后续插入的元素下标一一对应:
//意思是,第一个数的下标就是 1,而不是0 ,同时也是 size() -1
vector 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 bigheap; //priority_queue 默认大根堆
priority_queue, greater > littleheap; // 如果要定义小根堆,就要写全参数
// priority_queue 的参数为: 数据类型、容器类型、定义类型。
//如果是小根堆, 我们在第三个参数那里改成: greater
//如果是大根堆:完整的写法就是: priority_queue , less > 堆的名字
然后,下面是一些堆的函数:
priority_queue big;
priority_queue, greater > little;
int main() {
big.push(1); //插入的同时自动调整位置
big.pop();//删除堆顶元素
big.top()//返回堆顶元素 最大值/最小值
}
下面给一道堆的模板题:
P3378 【模板】堆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3378答案:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
#include
#include
下面讲一下,二叉堆的综合运用:
1. 对顶堆
先给一个模板题,来看看对顶堆的使用场景
P1801 黑匣子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://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
#include
#include
#include
#include
#include
#include
#include
#include
有一天在写洛谷的一道题的时候,我想出来大概思路,但是有几步我想破头也无法实现。
后来看了题解,发现原来结构体可以这样使用。
比如,现在有一个结构体:
struct person {
char gender;
int age, high, height;
};
它表示的是一个人的一些信息。
然后又给你一个vector容器,里面装的是person类型的元素
vector a;
嗯。。如果我们要将gender,age,high,height都导入进容器里面应该怎么做?
vector a;
int main() {
a.push_back(person{ 'F', 18, 180, 150});
person t = a[0];
cout << t.age;
}
只需要按照这个格式:
push_back( 结构体名字{ // 按照成员变量定义的顺序写你想要赋的值// } )
切记,一定要按照顺序结构体里的顺序赋值。
好的,现在我们给出那道题目:
P1878 舞蹈课 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1878
简述一下,我第一次想的思路是将 (异性 and 相邻的差值 )插入最小堆里面
因为,题目要求,如果差值相等,输出最左边的一对,所以我们需要记录每个人站的编号。
然后又因为,如果出列了一对,那么这一对的左右两边的人要自动补齐,所以这样就又多了一对的数据,把这一对数据插入最小堆即可。 这一对的数据很好得到,就是出列的那一对的左边和右边,我们应该怎么把它们得到?所以我当时想到了链表。
嗯。。。不过我足足调试了两小时,因为初步的内容很粗糙,细节不够到位。
最后我想了一个很绝的方法来杜绝0,n+1,直接给0,n+1性别赋值z
然后如果 前面的后面的加起来 == N+B 或者相减== N-B
再加上本地ide背锅(明明最后的答案是对的,但是编辑器不给过,就浪费了好久时间,后面无奈交了,发现对了。。无语)
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
#include
#include