目录
题目三——1046. 最后一块石头的重量 - 力扣(LeetCode)
题目四——703. 数据流中的第 K 大元素 - 力扣(LeetCode)
题目五——P2085 最小函数值 - 洛谷 | 计算机科学教育新生态
题目六——P1631 序列合并 - 洛谷 | 计算机科学教育新生态
题目七——P1878 舞蹈课 - 洛谷 | 计算机科学教育新生态
题目八——692. 前K个高频单词 - 力扣(LeetCode)
题目九——295. 数据流的中位数 - 力扣(LeetCode)
1.堆
首先我们得知道堆是啥?
如果想要详细了解这个堆的话可以去下面这4篇文章看看
- 堆的介绍,堆的向下调整算法,堆的向上调整算法_堆调整-CSDN博客
- 堆的基本操作(c语言实现)_c语言堆的基本操作-CSDN博客
- 堆的应用1——堆排序-CSDN博客
- 堆的应用2——TOPK问题-CSDN博客
但是我还是要简单的介绍一下,什么是堆?
堆在计算机科学中有广泛的应用,尤其是在实现优先队列和堆排序算法中。优先队列是一种数据结构,其中元素的优先级决定了它们的出队顺序。堆可以作为一种高效的优先队列实现方式,因为堆顶元素总是优先级最高(最大或最小)的元素。堆排序算法则利用堆的性质,通过构建最大堆或最小堆,并反复取出堆顶元素来实现排序。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
堆的分类
堆主要分为两种类型:最大堆(Max Heap)和最小堆(Min Heap)。
- 在最大堆中,父节点的值总是大于或等于其子节点的值,因此堆顶元素是整个堆中的最大值。
- 相反,在最小堆中,父节点的值总是小于或等于其子节点的值,堆顶元素是整个堆中的最小值。
我们先练习一下什么是大根堆?什么是小根堆?
2.优先级队列 (priority_queue的中文翻译)
普通的队列是⼀种先进先出的数据结构,即元素插⼊在队尾,⽽元素删除在队头。
⽽在优先级队列中,元素被赋予优先级,当插⼊元素时,同样是在队尾,但是会根据优先级进⾏位置调整,优先级越⾼,调整后的位置越靠近队头;
同样的,删除元素也是根据优先级进⾏,优先级最⾼ 的元素(队头)最先被删除。
其实可以认为,优先级队列就是堆实现的⼀个数据结构。 priority_queue 就是C++提供的,已经实现好的优先级队列,底层实现就是⼀个堆结构。
在算法竞赛 中,如果是需要使⽤堆的题⽬,⼀般就直接⽤现成的priority_queue,很少⼿写⼀个堆,因为省事~
3.创建priority_queue-初阶
优先级队列的创建结果有很多种,因为需要根据实际需求,可能会创建出各种各样的堆。这些堆可能包括:
- 简单内置类型的大根堆或小根堆:比如存储int类型的大根堆或小根堆。
- 存储字符串的大根堆或小根堆。
- 存储自定义类型的大根堆或小根堆:比如堆里面的数据是一个结构体。
关于每一种创建结果,都需要有与之对应的写法。在初阶阶段,先用简单的int类型建堆,重点学习priority_queue的用法。
注意:priority_queue包含在<queue>这个头文件中。
看看最简单的int类型的大根堆
#include <queue>
#include <vector>
#include <iostream>
using namespace std;
int main() {
// 创建一个空的priority_queue,默认是大根堆,基础容器类型是vector
priority_queue<int> pq;
// 插入元素
pq.push(1);
pq.push(4);
pq.push(2);
pq.push(8);
// 输出并移除顶部元素(优先级最高的元素)
while (!pq.empty()) {
cout << pq.top() << " "; // 输出8 4 2 1
pq.pop();
}
return 0;
}
看看另外两种初始化类型
在C++中,priority_queue
是一个模板类,它通常用于实现堆数据结构。堆是一种特殊的完全二叉树,它满足堆性质:对于大根堆(max-heap),每个节点的值都大于或等于其子节点的值;对于小根堆(min-heap),每个节点的值都小于或等于其子节点的值。
priority_queue
的模板参数有三个:
- 数据类型:这是堆中存储的元素类型。
- 存数据的结构:这是底层容器类型,用于实际存储堆中的元素。
priority_queue
默认使用std::vector
作为底层容器,但你也可以指定其他容器类型(只要它支持随机访问迭代器)。 - 数据之间的比较方式:这是一个函数对象(通常是一个仿函数或函数指针),用于比较堆中的元素,以确定它们的优先级。默认情况下,
priority_queue
使用std::less
作为比较方式,这创建了一个大根堆。如果你想要一个小根堆,可以使用std::greater
。
现在,让我们具体讲解你给出的代码:
创建大根堆
priority_queue<int, vector<int>, less<int>> heap2; // 也是大根堆
- 数据类型:
int
。这意味着堆中存储的元素是整数。 - 存数据的结构:
vector<int>
。这指定了底层容器是一个整数向量。这是可选的,因为priority_queue
默认使用std::vector
。 - 数据之间的比较方式:
less<int>
。这是一个函数对象,它比较两个整数并返回true
如果第一个整数小于第二个整数(在实际上,less<int>
的实现会返回false
,因为我们是用它来构建大根堆的,但这里的逻辑是反向的——即,如果a < b
则返回false
意味着a
的优先级不低于b
,因此a
应该留在堆顶或更高的位置)。然而,在这个特定的例子中,使用less<int>
实际上是多余的,因为priority_queue
默认就使用std::less
来创建大根堆。
因此,heap2
是一个大根堆,它存储整数,使用向量作为底层容器,并使用默认的(或显式指定的)std::less<int>
比较函数来确定元素的优先级。在这个堆中,优先级最高的元素(即值最大的元素)总是在堆顶。
创建小根堆
如果你想要创建一个小根堆,你可以这样做:
priority_queue<int, vector<int>, greater<int>> heap3; // 小根堆
在这个例子中,greater<int>
是一个函数对象,它比较两个整数并返回true
如果第一个整数大于第二个整数(实际上,对于小根堆来说,如果a > b
则返回false
意味着a
的优先级不高于b
,因此b
应该留在堆顶或更高的位置)。这样,heap3
就是一个小根堆,其中值最小的元素总是在堆顶。
4.priority_queue常用的接口函数
size 和 empty函数
-
size:返回优先级队列中元素的个数。
-
empty:检查优先级队列是否为空。
O(1)时间复杂度:size
和empty
操作都可以在常数时间内完成。
push函数
-
往优先级队列里面添加一个元素。
时间复杂度:由于底层是一个堆结构,所以添加元素的时间复杂度为O(log n),其中n是优先级队列中的元素数量。
pop函数
-
删除优先级最高的元素。
时间复杂度:由于底层是一个堆结构,所以删除优先级最高的元素的时间复杂度为O(log n),其中n是优先级队列中的元素数量。
top函数
-
获取优先级最高的元素,但不删除它。
时间复杂度:O(1)。获取优先级最高的元素可以在常数时间内完成,因为这个元素总是位于堆的顶端(对于大根堆)或底端(对于小根堆,取决于具体实现)。
接下来我们好好举一个例子来说明
#include <iostream>
#include <queue>
#include <vector>
int main() {
// 创建一个空的优先级队列(大根堆)
std::priority_queue<int> pq;
// 检查队列是否为空
if (pq.empty()) {
std::cout << "Priority queue is initially empty." << std::endl;
}
// 向队列中添加元素
pq.push(10);
pq.push(20);
pq.push(15);
// 获取队列的大小
std::cout << "Size of priority queue after pushes: " << pq.size() << std::endl;
// 获取并输出优先级最高的元素(但不删除)
int topElement = pq.top();
std::cout << "Top element (highest priority): " << topElement << std::endl; // 输出20
// 删除优先级最高的元素
pq.pop();
// 再次获取队列的