C++ 竞赛训练营第三课:STL 核心容器之 priority_queue

C++ 竞赛训练营第三课:STL 核心容器之 priority_queue(优先队列)(竞赛向)

一、课程导航 🚀

  1. 🎯 竞赛视角:priority_queue 的 "效率核心"------ 为什么是贪心 / TopK 问题的最优解?
  2. 📚 核心特性:底层堆结构与优先级规则(大根堆 / 小根堆)
  3. 🔧 竞赛高频操作:构造配置、增删查改与接口实战
  4. ⚡ 竞赛进阶用法:自定义优先级(结构体 / 多字段排序)
  5. 📝 真题实战:贪心算法、TopK 问题、Dijkstra 算法优化
  6. ❌ 竞赛避坑指南:优先级配置错误、效率陷阱规避
  7. 🔖 下节预告:STL 核心容器之 map/unordered_map(哈希表的竞赛用法)

二、核心知识点与竞赛实战

🎯 1. 竞赛视角:priority_queue 的 "效率核心"

在竞赛中,priority_queue(优先队列)是 "动态维护最值" 的神器,核心价值在于:

  • 自动排序:插入元素时自动按优先级排序,队首始终是最值(大根堆默认最大,小根堆默认最小);
  • 高效操作:插入(push)、删除最值(pop)均为 O (log n) 时间,远超手动遍历找最值的 O (n);
  • 场景适配:完美匹配贪心算法(每次选当前最优)、TopK 问题(维护前 k 个最值)、图算法(Dijkstra 最短路径)等竞赛高频题型,是 "空间换时间" 的典型高效方案。

竞赛场景适配

  • 贪心算法:哈夫曼编码、任务调度、最小代价合并、区间覆盖;
  • TopK 问题:第 k 大元素、前 k 个高频元素、数据流中的中位数;
  • 图算法:Dijkstra 最短路径(优先队列优化)、最小生成树辅助。

📚 2. 核心特性:底层堆结构与优先级规则

2.1 底层原理与核心规则
  • 底层实现:基于堆结构(完全二叉树),无需手动维护排序,容器自动调整;
  • 核心规则:优先级驱动存取,队首永远是 "优先级最高" 的元素(非 FIFO,区别于普通 queue);
  • 默认行为:大根堆(priority_queue<int> pq),队首为最大值。
2.2 大根堆与小根堆(竞赛必备配置)

|-----|------------|----------------------------------------------------------|------------------------|
| 类型 | 核心特性(队首元素) | 竞赛配置代码(直接套用) | 适用场景 |
| 大根堆 | 最大值优先 | priority_queue<int> pq; | 找最大元素、贪心选最大项 |
| 小根堆 | 最小值优先 | priority_queue<int, vector<int>, greater<int>> pq; | 找最小元素、TopK 问题、Dijkstra |

2.3 与 queue 的核心区别(竞赛选型关键)

|----------------|------------|--------|-------------------|---------------------|
| 容器 | 存取规则 | 队首元素 | 核心操作复杂度 | 竞赛适用场景 |
| queue | FIFO(先进先出) | 最早入队元素 | push/pop O(1) | BFS、层序遍历、滑动窗口(普通) |
| priority_queue | 优先级优先 | 最值元素 | push/pop O(log n) | 贪心、TopK、Dijkstra 算法 |

🔧 3. 竞赛高频操作:构造配置与接口实战

priority_queue接口与普通 queue 高度兼容,竞赛中仅需掌握 "核心 4 接口 + 2 构造",简洁高效:

3.1 核心构造方式(竞赛直接套用)
复制代码
cpp 复制代码
// 1. 大根堆(默认,最常用)

priority_queue<int> pq1;

// 2. 小根堆(竞赛高频,必须掌握)

priority_queue<int, vector<int>, greater<int>> pq2;

// 3. 用已有数组/vector初始化(快速构造堆)

vector<int> nums = {3,1,4,1,5};

priority_queue<int> pq3(nums.begin(), nums.end()); // 大根堆,初始元素为nums所有值

// 4. 结构体自定义优先级(见4.1节)
3.2 核心接口(竞赛必备,O (log n) 复杂度)
复制代码
cpp 复制代码
priority_queue<int> pq;

pq.push(x); // 插入元素x,自动调整堆结构(O(log n))

pq.pop(); // 删除队首最值元素(O(log n)),无返回值!

pq.top(); // 访问队首最值元素(O(1)),必须先判空

pq.empty(); // 判断队列是否为空(O(1)),访问top/pop前必用

pq.size(); // 获取元素个数(O(1)),竞赛中用于边界判断(如TopK中控制堆大小)
3.3 竞赛关键提醒(避坑第一)
  • 无返回值陷阱:pop()仅删除队首元素,不返回值(区别于 vector),需先top()获取再pop();
  • 判空必做:top()/pop()前必须用empty()判断,否则空容器访问会导致 RE(运行时错误);
  • 小根堆配置:第三个参数greater<int>不可省略,且需包含<vector>头文件(通常#include<bits/stdc++.h>已涵盖)。

⚡ 4. 竞赛进阶用法:自定义优先级(结构体 / 多字段排序)

竞赛中常需对结构体(如学生、任务、边)按自定义规则排序,priority_queue支持通过 "仿函数" 或 "重载运算符" 实现,以下是竞赛最常用的 "仿函数配置法":

4.1 结构体自定义优先级(高频真题场景)

示例场景:按学生成绩降序排序(成绩相同按年龄升序)

复制代码
cpp 复制代码
#include <iostream>

#include <priority_queue>

using namespace std;

// 定义结构体(竞赛中直接复制使用)

struct Student {
	
	int score; // 成绩
	
	int age; // 年龄
	
};

// 定义仿函数(自定义优先级规则)

struct Cmp {
	
// 重载()运算符:返回true时,a的优先级低于b(即b排在队首)
	
	bool operator()(const Student& a, const Student& b) {
		
		if (a.score != b.score) {
			
			return a.score < b.score; // 成绩降序(分数高的优先)
			
		} else {
			
			return a.age > b.age; // 成绩相同,年龄升序(年龄小的优先)
			
		}
		
	}
	
};

// 竞赛中使用配置

priority_queue<Student, vector<Student>, Cmp> pq;

核心规则:仿函数中return a < b表示 "a 的优先级低于 b",即 b 排在队首(与sort的比较器逻辑相反,注意区分)。

4.2 pair 类型的优先级(竞赛简化用法)

pair默认按 "first 元素降序,first 相同则 second 降序" 排序,竞赛中可直接利用这一特性,无需自定义仿函数:

复制代码
cpp 复制代码
// 示例:pair<int, int>(first=分数,second=年龄),按分数降序、年龄升序

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

// 注:greater<pair<int,int>> 会让pair按first升序,如需降序可省略(默认大根堆)

📝 5. 真题实战:竞赛高频题模板(直接套用)

例题 1:TopK 问题 ------ 数组中的第 k 个最大元素(LeetCode 215,入门题)

题目描述:给定整数数组和 k,返回数组中第 k 个最大元素(无需排序整个数组)。

核心思路:小根堆维护前 k 个最大元素,堆顶即为第 k 大元素(O (n log k) 高效解)。

竞赛代码(模板)

复制代码
cpp 复制代码
#include <iostream>

#include <vector>

#include <priority_queue>

using namespace std;

int findKthLargest(vector<int>& nums, int k) {
	
	priority_queue<int, vector<int>, greater<int>> pq; // 小根堆
	
	for (int x : nums) {
		
		pq.push(x);
		
		if (pq.size() > k) { // 堆大小超过k,删除最小值(保证堆内是前k大)
			
			pq.pop();
			
		}
		
	}
	
	return pq.top(); // 堆顶是第k大元素
	
}

int main() {
	
	vector<int> nums = {3,2,1,5,6,4};
	
	int k = 2;
	
	cout << findKthLargest(nums, k) << endl; // 输出5
	
	return 0;
	
}
例题 2:贪心算法 ------ 最小代价合并石头(竞赛经典)

题目描述:有 n 堆石头,每次合并两堆,代价为两堆石头数量之和,求合并所有堆的最小代价。

核心思路:小根堆每次选最小的两堆合并,合并后将新堆入堆,重复至只剩一堆(哈夫曼编码思想)。

竞赛代码(模板)

复制代码
cpp 复制代码
#include <iostream>

#include <vector>

#include <priority_queue>

using namespace std;

int minCost(vector<int>& stones) {
	
	priority_queue<int, vector<int>, greater<int>> pq(stones.begin(), stones.end());
	
	int cost = 0;
	
	while (pq.size() > 1) {
		
		int a = pq.top(); pq.pop();
		
		int b = pq.top(); pq.pop();
		
		int sum = a + b;
		
		cost += sum;
		
		pq.push(sum);
		
	}
	
	return cost;
	
}

int main() {
	
	vector<int> stones = {4,3,2,1};
	
	cout << minCost(stones) << endl; // 输出19(1+2=3→3+3=6→6+4=10,总3+6+10=19)
	
	return 0;
	
}
例题 3:图算法 ------Dijkstra 最短路径(优先队列优化)

题目描述:给定有向带权图,求从起点到所有节点的最短路径。

核心思路:用小根堆维护 "当前节点到起点的最短距离",每次选距离最小的节点扩展,更新邻接节点距离(O (m log n),m 为边数,n 为节点数)。

竞赛代码(模板)

复制代码
cpp 复制代码
#include <iostream>

#include <vector>

#include <priority_queue>

#include <climits>

using namespace std;

const int INF = INT_MAX;

int main() {
	
	int n, m, start; // n节点数,m边数,start起点
	
	cin >> n >> m >> start;
	
	vector<vector<pair<int, int>>> adj(n+1); // adj[u]存储(u, v, w):u到v的边权w
	
	for (int i = 0; i < m; ++i) {
		
		int u, v, w;
		
		cin >> u >> v >> w;
		
		adj[u].emplace_back(v, w);
		
	}
	
	vector<int> dist(n+1, INF); // dist[i]:起点到i的最短距离
	
	dist[start] = 0;
	
// 小根堆:存储(当前距离, 节点),按距离升序
	
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
	
	pq.emplace(0, start);
	
	while (!pq.empty()) {
		
		auto [d, u] = pq.top(); pq.pop();
		
		if (d > dist[u]) continue; // 已找到更短路径,跳过
		
		for (auto [v, w] : adj[u]) {
			
			if (dist[v] > dist[u] + w) {
				
				dist[v] = dist[u] + w;
				
				pq.emplace(dist[v], v);
				
			}
			
		}
		
	}
	
	for (int i = 1; i <= n; ++i) {
		
		if (dist[i] == INF) cout << "INF ";
		
		else cout << dist[i] << " ";
		
	}
	
	return 0;
	
}

❌ 6. 竞赛避坑指南:常见错误与效率陷阱

6.1 常见错误(最易丢分)
  • 错误 1:小根堆配置遗漏参数(如写成priority_queue<int, greater<int>> pq)------ 缺少底层容器参数vector<int>,编译报错;
  • 错误 2:自定义优先级时逻辑颠倒(如想按成绩降序,却写成return a.score > b.score)------ 仿函数返回true表示 a 优先级低于 b,需注意与sort比较器的区别;
  • 错误 3:空容器调用top()/pop()------ 竞赛中必须先判空(if (!pq.empty())),否则直接 RE;
  • 错误 4:混淆top()和front()------priority_queue无front()接口,访问队首用top()(与 queue 区别)。
6.2 效率陷阱(避免超时)
  • 错误:对大规模数据(n>1e5)使用priority_queue时,频繁调用size()判断 ------ 虽size()是 O (1),但需避免不必要的循环判断,可通过 "堆内元素特性" 终止(如 Dijkstra 中跳过已处理节点);
  • 正确:TopK 问题中,堆大小严格控制在 k 以内(超过则pop()),避免 O (n log n) 开销,保证 O (n log k) 效率;
  • 注意:结构体自定义优先级时,尽量用 "仿函数" 而非 lambda(部分编译器对 lambda 支持不佳,竞赛中优先仿函数保证兼容性)。

🔖 7. 下节预告:STL 核心容器之 map/unordered_map(哈希表的竞赛用法)

  1. 核心特性:键值对存储、查找 O (1)(unordered_map)与有序性(map);
  2. 竞赛高频用法:快速查找、统计频率、映射关系(如字符串→整数、id→属性);
  3. 核心区别:map(红黑树,有序,O (log n))与 unordered_map(哈希表,无序,O (1))的选型;
  4. 真题适配:两数之和、字母异位词、最长连续序列、哈希表优化动态规划。

下节课,我们将聚焦哈希表的竞赛实战 ------ 如何用 map/unordered_map 解决 "快速查找" 类高频题,掌握 O (1) 查找的核心效率优势!

相关推荐
小年糕是糕手1 小时前
【C++】类和对象(三) -- 拷贝构造函数、赋值运算符重载
开发语言·c++·程序人生·考研·github·个人开发·改行学it
艾莉丝努力练剑1 小时前
【C++:C++11收尾】解构C++可调用对象:从入门到精通,掌握function包装器与bind适配器包装器详解
java·开发语言·c++·人工智能·c++11·右值引用
卿雪1 小时前
MySQL【索引】篇:索引的分类、B+树、创建索引的原则、索引失效的情况...
java·开发语言·数据结构·数据库·b树·mysql·golang
CNRio1 小时前
第七章-DockerSwarm:容器集群的‘指挥官‘
java·开发语言·容器
m0_740043731 小时前
JavaScript
开发语言·javascript·ecmascript
八月的雨季 最後的冰吻1 小时前
FFmepg--29- C++ 音频混音器实现
开发语言·c++·音视频
wjm0410061 小时前
秋招ios面试 -- 真题篇(一)
开发语言·ios·面试
拾光Ծ1 小时前
“异常”处理机制 与 C++11中超实用的 “智能指针”
java·开发语言·c++·安全
枫叶丹41 小时前
【Qt开发】Qt窗口(五) -> 非模态/模态对话框
c语言·开发语言·数据库·c++·qt