当前位置: 首页 > news >正文

[数据结构与算法] 优先队列 | 最小堆 C++

下面是关于 C++ 中 std::priority_queue 的详细说明,包括初始化、用法和常见的应用场景。


什么是 priority_queue

priority_queue(优先队列)是 C++ 标准库中的一个容器适配器。它和普通队列(queue)最大的不同在于,元素的出队顺序不是根据它们入队的顺序(先进先出),而是根据它们的优先级

默认情况下,priority_queue 是一个大顶堆(Max-Heap),意味着优先级最高的元素是值最大的元素。每次调用 top() 都会返回当前队列中最大的元素。

  • 底层实现:通常使用**堆(Heap)**数据结构来实现,这使得它能以对数时间复杂度(O(log n))插入元素和删除最大(或最小)的元素。
  • 头文件:使用前需要包含头文件 <queue>
#include <iostream>
#include <queue>
#include <vector>
#include <functional> // 包含 std::greater 和 std::less

1. priority_queue 的初始化

priority_queue 的模板定义如下:

template <class T, class Container = std::vector<T>, class Compare = std::less<typename Container::value_type>>
class priority_queue;
  • T: 存储的元素类型。
  • Container: 用于存储元素的底层容器,默认为 std::vector<T>。必须是支持随机访问迭代器和 front(), push_back(), pop_back() 等操作的序列容器,例如 std::vectorstd::deque
  • Compare: 比较函数或函数对象,用于确定元素的优先级。默认为 std::less<T>,它会创建一个大顶堆。
1.1 默认初始化(大顶堆)

最常见的用法,创建一个存储 int 类型的大顶堆。每次 top() 都会返回最大的元素。

// 默认情况下,是 std::vector 作为容器,std::less 作为比较函数,即大顶堆
std::priority_queue<int> max_heap;max_heap.push(30);
max_heap.push(100);
max_heap.push(25);
max_heap.push(40);// 此刻队列顶部的元素是 100
std::cout << "Top element is: " << max_heap.top() << std::endl; // 输出 100
1.2 初始化为小顶堆

如果你想让 top() 总是返回最小的元素,你需要创建一个小顶堆(Min-Heap)。这需要显式提供所有模板参数:

  • T: 元素类型。
  • Container: 底层容器,通常是 std::vector<T>
  • Compare: 比较函数,使用 std::greater<T>
// 使用 std::greater<int> 来创建小顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;min_heap.push(30);
min_heap.push(100);
min_heap.push(25);
min_heap.push(40);// 此刻队列顶部的元素是 25
std::cout << "Top element is: " << min_heap.top() << std::endl; // 输出 25
1.3 存储自定义结构体

当存储自定义类型(如 structclass)时,你需要告诉优先队列如何比较它们。有两种主要方法:

方法一:重载 < 运算符(用于大顶堆)

struct Node {int id;int priority;// 重载 < 运算符,priority 越大,优先级越高bool operator<(const Node& other) const {return this->priority < other.priority;}
};std::priority_queue<Node> pq;
pq.push({1, 10});
pq.push({2, 5});
pq.push({3, 20});// top() 将返回 priority 最大的 Node,即 {3, 20}
std::cout << "Top node: id=" << pq.top().id << ", priority=" << pq.top().priority << std::endl;

方法二:提供自定义比较器(更灵活)

如果你不想或不能修改结构体定义,或者需要多种排序方式,可以提供一个自定义的比较函数对象(Functor)。

struct Node {int id;int priority;
};// 自定义比较器结构体
struct CompareNode {// 返回 true 表示 a 的优先级低于 bbool operator()(const Node& a, const Node& b) {// 创建小顶堆,priority 越小,优先级越高return a.priority > b.priority;}
};std::priority_queue<Node, std::vector<Node>, CompareNode> pq;
pq.push({1, 10});
pq.push({2, 5});
pq.push({3, 20});// top() 将返回 priority 最小的 Node,即 {2, 5}
std::cout << "Top node: id=" << pq.top().id << ", priority=" << pq.top().priority << std::endl;

使用 Lambda 表达式(C++11及以上)也可以实现,但定义比较器时需要 decltype

1.4 从已有容器初始化

你可以用一个已有的容器(如 vector)中的元素来初始化优先队列。

std::vector<int> data = {30, 100, 25, 40};// 从 data 初始化一个大顶堆
std::priority_queue<int> max_heap_from_vec(data.begin(), data.end());std::cout << "Top element from vector init: " << max_heap_from_vec.top() << std::endl; // 输出 100// 初始化小顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap_from_vec(data.begin(), data.end());
std::cout << "Top element from vector init (min-heap): " << min_heap_from_vec.top() << std::endl; // 输出 25

2. priority_queue 的常用操作

优先队列的操作非常简单直观:

  • push(element): 向队列中插入一个元素。时间复杂度 O(log n)。
  • pop(): 移除队首(优先级最高)的元素。时间复杂度 O(log n)。
  • top(): 返回队首(优先级最高)元素的常引用,不删除元素。时间复杂度 O(1)。
  • empty(): 检查队列是否为空。
  • size(): 返回队列中元素的数量。

示例代码:

std::priority_queue<int> pq;std::cout << "Is queue empty? " << (pq.empty() ? "Yes" : "No") << std::endl;pq.push(10);
pq.push(50);
pq.push(20);std::cout << "Queue size: " << pq.size() << std::endl;
std::cout << "Top element: " << pq.top() << std::endl; // 50pq.pop(); // 移除了 50std::cout << "Top element after pop: " << pq.top() << std::endl; // 20// 遍历并清空优先队列
std::cout << "Queue elements in priority order: ";
while (!pq.empty()) {std::cout << pq.top() << " ";pq.pop();
}
std::cout << std::endl;
// 输出: Queue elements in priority order: 20 10

注意priority_queue 没有提供迭代器,不能像 vector 那样直接遍历。唯一的访问方式就是通过 top() 获取队首元素。


3. priority_queue 的应用场景

优先队列在算法和系统设计中非常有用,尤其适合处理那些需要动态维护“最大/最小”元素的问题。

3.1 Top-K 问题

这是最经典的应用。例如,从一个巨大的数据流中找出最大的 K 个元素。

思路

  • 维护一个大小为 K 的小顶堆
  • 遍历数据流,对于每个新元素:
    • 如果堆的大小小于 K,直接将元素插入堆中。
    • 如果堆的大小等于 K,将新元素与堆顶元素(当前 K 个元素中最小的那个)比较。
    • 如果新元素大于堆顶元素,则弹出堆顶,并将新元素插入。
  • 遍历结束后,堆中剩下的 K 个元素就是最大的 K 个元素。

为什么用小顶堆? 因为小顶堆的堆顶是“门槛”,只有比这个门槛大的元素才有资格进入这个“Top-K 圈子”,这样可以高效地淘汰掉不够大的元素。

3.2 堆排序(Heap Sort)

虽然 C++ 提供了 std::sort,但优先队列可以很自然地实现堆排序。

  1. 将所有元素插入一个大顶堆。
  2. 依次从堆中取出堆顶元素,放入结果数组中。取出的顺序就是从大到小。
3.3 Dijkstra 算法

在图论中,Dijkstra 算法用于寻找图中单源最短路径。算法的核心是每次都从“待处理”的顶点中,选择一个距离源点最近的顶点进行扩展。优先队列(小顶堆)可以完美地胜任这个任务。

  • 优先队列中存储 pair<int, int>,即 {距离, 顶点编号}
  • 每次从队列中取出 top(),就是当前距离源点最近的未访问顶点。
3.4 Huffman 编码

在数据压缩中,Huffman 编码算法使用优先队列来构建最优二叉树(Huffman 树)。

  • 将每个字符及其频率作为一个节点放入一个优先队列(小顶堆)中,频率越小,优先级越高。
  • 每次从队列中取出两个频率最小的节点,合并成一个新的父节点(频率为两者之和),再将新节点放回优先队列。
  • 重复此过程,直到队列中只剩一个节点,即 Huffman 树的根。
3.5 任务调度系统

在操作系统或中间件中,任务调度器需要从多个待执行的任务中选择一个优先级最高的来执行。优先队列可以用来管理这些任务,top() 总是返回当前最紧急的任务。


希望这份详细的说明对你有帮助!

http://www.dtcms.com/a/273238.html

相关文章:

  • 几种LLM推理加速技术的区别
  • 列表页与详情页的智能识别:多维度判定方法与工业级实现
  • 海光芯赋能:国产化高性能计算平台,重塑边缘与工业智能新算力
  • 使用虚拟机远程登陆ensp模拟器交换机
  • ROS1学习第二弹
  • 1 C++提高——模板
  • H5微应用四端调试工具—网页版:深入解析与使用指南
  • FS-TAS如何提升电催化反应的效率-测试GO
  • 人大金仓下载安装教程总结
  • 区块链基础知识:从比特币到区块链的全面解析
  • 复杂度简介
  • Android-jetpack之DataBinding实战应用
  • NMEA-0183 协议 GPS 介绍
  • Redis-集群Cluster
  • Python练习(1)Python基础类型操作语法实战:20道实战题解与案例分析(上)
  • 《一起出发,“春”不“晚”》特别行动踏梦武当,探寻新春奇境
  • 教育领域AI教师培训计划及相关行业动态的综合简报
  • CVPR2022——RepLKNet模型有效感受野的热图可视化
  • Java Stream流:高效数据处理全解析
  • RV1126平台(Buildroot Linux)+ SunplusIT SPCA2688 USB摄像头 RTSP推流全流程复盘与问题解决记录
  • LabelImg标注工具详解与使用教程
  • SQL进阶:自连接的用法
  • 数字电子时钟——数字电路课设
  • 存在两个cuda环境,在conda中切换到另一个
  • vue3中ref和reactive的使用、优化
  • ABP vNext + OpenIddict:多租户授权中心
  • 二分查找篇——搜索旋转排序数组【LeetCode】两次二分查找
  • PPT 倒计时工具:把控节奏,掌握时间,超简单超实用让演示游刃有余
  • linux的DNS域名解析服务
  • Python操作redis数据库:终端下载安装命令、redis关键特性说明、redis的数据类型、redis的连接两种方式