深入解析 STL 优先级队列:从原理到实战

深入解析 STL 优先级队列:从原理到实战

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》

《C++入门到进阶&自我学习过程记录》

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

一、priority_queue 核心逻辑

  1、本质与优先级规则

  2、底层容器的适配要求

  3、常用接口与场景示例

二、priority_queue 底层实现解析

  1、默认 “大堆” 规则排序模拟实现

  2、仿函数

    2.1 仿函数的结构

    2.2 仿函数测试代码

    2.3 利用仿函数进行 priority_queue 底层实现

    2.4 测试代码演示

三、OJ实战练习:数组中第k个最大的元素

结束语


在 STL 中,priority_queue(优先队列)是处理优先级任务的实用工具 —— 以堆结构为核心,封装复杂排序逻辑,能高效获取优先级最高的元素(默认是大的值优先级高),广泛用于 Top K、任务调度等场景。本文将拆解其特性、接口与原理,帮你从 “会用” 到 “懂底层原理”。

一、priority_queue 核心逻辑

  1、本质与优先级规则

  • 容器适配器属性:不直接存储数据,而是封装底层容器(如 vector、deque),并通过堆算法(make_heap、push_heap、pop_heap)来维护堆结构。
  • 优先级规则:默认是大堆(堆顶为最大元素),可通过仿函数指定比较规则改为小堆;优先级由比较函数决定,支持内置类型和自定义类型。
参考文档:priority_queue - C++ Reference

  2、底层容器的适配要求

      底层容器需支持 “随机访问迭代器” 和以下操作:

  • empty():检测是否为空
  • size():返回元素个数
  • front():访问堆顶元素(即容器首元素)
  • push_back():在容器尾部插入元素(用于后续堆调整算法)
  • pop_back():删除容器尾部元素(配合堆调整算法移除堆顶数据)

      STL 中默认使用 vector(空间连续,堆算法效率更高),也可指定 deque

  3、常用接口与场景示例

接口声明功能说明
priority_queue()构造一个空的优先队列,初始化后无任何元素,需通过 push(x) 插入数据。
priority_queue(first, last)利用迭代器区间 [ first, last ] 中的所有元素初始化优先队列,初始化后会自动调整为堆结构
empty()检测优先队列是否为空,若队列中无元素则返回 true,存在元素则返回 false,常用于判断是否可执行 top() 或 pop() 操作。
size()返回优先队列中有效元素的个数,可用于了解队列数据量,或配合循环控制 pop() 操作次数(如 Top K 问题中弹出前 K - 1 个元素)。
top()返回堆顶元素的引用,大堆场景下返回队列中的最大值,小堆场景下返回最小值;需注意,调用前需用 empty() 确认队列非空,否则会触发未定义行为。
push(x)将元素 x 插入优先队列的尾部,插入后会自动调用堆算法 (push_heap) 调整堆结构,确保队列仍满足大堆或小堆的排序规则
pop()删除优先队列的堆顶元素,删除前会先通过堆算法(pop_heap)将堆顶元素交换到队列尾部,再执行删除;操作后需确保队列非空,且删除后仍保持堆结构

接口测试代码:

//Test.cpp #include<iostream> #include<queue> using namespace std; int main() { //priority_queue接口使用 priority_queue<int> pq1;//默认底层基于vector容器,且为大堆 pq1.push(4); pq1.push(7); pq1.push(2); pq1.push(5); pq1.push(10); pq1.push(8); while (!pq1.empty()) { cout << pq1.top() << " "; pq1.pop(); } cout << endl; priority_queue<int, vector<int>, greater<int>> pq2;//调整成默认是小的优先级高(小堆) pq2.push(4); pq2.push(7); pq2.push(2); pq2.push(5); pq2.push(10); pq2.push(8); while (!pq2.empty()) { cout << pq2.top() << " "; pq2.pop(); } cout << endl; return 0; }

二、priority_queue 底层实现解析

      由于优先级队列的底层结构是堆,所以就会用到在之前所学数据结构堆中的相关功能的实现(如向上调整算法、向下调整算法等),如果不清楚的或者想要回顾之前的知识可以看下面我的往期文章:

数据结构之二叉树-堆

  1、默认 “大堆” 规则排序模拟实现

//Priority_Queue.h #include<iostream> #include<vector> using namespace std; namespace MyPriorityQueue { template<class T, class Container = vector<T>> class priority_queue { public: //向上调整算法(已知孩子位置推算父亲位置) void AdjustUp(size_t child)//访问下标 { size_t father = (child - 1) / 2; while (child > 0) //当child等于0说明已经调整到头节点 { //默认调整为大堆 if (_con[child] > _con[father]) { swap(_con[child], _con[father]); child = father; father = (child - 1) / 2; } else { break;//如果_con[child] <= _con[father]则说明调整完成 } } } //向下调整算法(已知父亲位置推算孩子位置) void AdjustDown(size_t father)//访问下标 { //假设父亲的左孩子为较大数 size_t child = father * 2 + 1; //默认调整为大堆 while (child < _con.size()) { //如果父亲存在右孩子且右孩子比左孩子大则进行修改 if ((child + 1) < _con.size() && _con[child] < _con[child + 1]) { child += 1; } if (_con[father] < _con[child]) { swap(_con[father], _con[child]); father = child; child = father * 2 + 1; } else { break;//如果父亲比较大孩子的值还大则说明已经调整完成了 } } } //插入数据(入优先级队列) void push(const T& x) { //首先尾插数据 _con.push_back(x); //再将该数据通过向上调整算法使得数组保持堆的结构 AdjustUp(_con.size() - 1); } //获取数据(队头数据) const T& top() { return _con[0]; } //删除数据(出优先级队列) void pop() { //首先首尾数据进行交换,将堆顶数据进行尾删 swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); //再将首位置数据通过向下调整算法使得数组保持堆的结构 AdjustDown(0); } //获取队列数据个数 size_t size() { return _con.size(); } //判空 bool empty() { return _con.size() == 0; } private: Container _con; }; }

测试代码:

//Test.cpp #include"Priority_Queue.h" //测试priority_queue模拟实现 void test_priority_queue() { MyPriorityQueue::priority_queue<int> pq; pq.push(4); pq.push(7); pq.push(2); pq.push(5); pq.push(10); pq.push(8); while (!pq.empty()) { cout << pq.top() << " "; pq.pop(); } cout << endl; } int main() { test_priority_queue(); return 0; }

  2、仿函数

      

      我们会发现标准库里面实现的优先级队列有第三个模板参数,而且上面我们讲了当优先级队列不传第三个参数默认是建立“大堆”,那我们如果想要优先级队列建立成“小堆”怎么实现呢?
      就需要用到接下来讲解的——仿函数

      仿函数顾名思义就是像一个函数,所以其实仿函数并不是一个函数而是一个类,其是通过重载 operator() 让一个类实例化以后所生成的对象能够像函数一样使用

    2.1 仿函数的结构

//仿函数 //通过重载 operator() 让一个类实例化以后所生成的对象能够像函数一样使用 //比较器:用于大堆(父节点优先级高于子节点时,返回true触发交换) template<class T> struct Less { bool operator()(const T& x, const T& y) { return x < y; } };

    2.2 仿函数测试代码

//Test.cpp //仿函数的测试 void test_Functor() { //函数对象 Less<int> LessFunc; cout << LessFunc(1, 2) << endl; //我们会发现 LessFunc(1, 2) 在结构上是非常类似函数的使用 //但实际上就是一个对象调用了重载 operator() 的结果 //也可以写出下面更加清晰的结构: cout << LessFunc.operator()(1, 2) << endl; } int main() { test_Functor(); return 0; }

    2.3 利用仿函数进行 priority_queue 底层实现

      上面我们只是简单介绍一下仿函数,仿函数的使用远没有这么简单,只是这里我们讲解仿函数是为了实现模板参数比较器 Compare,让 priority_queue 不仅能实现大堆也能实现小堆:

//Priority_Queue.h //仿函数 //通过重载 operator() 让一个类实例化以后所生成的对象能够像函数一样使用 //比较器:用于大堆(父节点优先级高于子节点时,返回true触发交换) template<class T> struct Less { bool operator()(const T& x, const T& y) { return x < y; } }; //比较器:用于小堆(子节点优先级高于父节点时,返回true触发交换) template<class T> struct Greater { bool operator()(const T& x, const T& y) { return x > y; } }; namespace MyPriorityQueue { template<class T, class Container = vector<T>, class Compare = Less<T>> // T:存储的元素类型;Container:底层容器(默认vector,需支持随机访问和尾部操作) // Compare:比较器(默认Less,即大堆) class priority_queue { public: //向上调整算法(已知孩子位置推算父亲位置) void AdjustUp(size_t child)//访问下标 { Compare com;// 实例化比较器,用于判断优先级 size_t father = (child - 1) / 2; while (child > 0) //当child等于0说明已经调整到头节点 { //if (_con[child] > _con[father]) if(com(_con[father], _con[child])) //需要注意Less是建立大堆,但是比较大小是 < ,父亲和孩子的顺序不要错 { swap(_con[child], _con[father]); child = father; father = (child - 1) / 2; } else { break; } } } //向下调整算法(已知父亲位置推算孩子位置) void AdjustDown(size_t father)//访问下标 { Compare com;// 实例化比较器,用于判断优先级 size_t child = father * 2 + 1; while (child < _con.size()) { //if ((child + 1) < _con.size() && _con[child] < _con[child + 1]) if ((child + 1) < _con.size() && com(_con[child], _con[child + 1])) { child += 1; } //if (_con[father] < _con[child]) if (com(_con[father], _con[child])) { swap(_con[father], _con[child]); father = child; child = father * 2 + 1; } else { break; } } } //插入数据(入优先级队列) void push(const T& x) { //首先尾插数据 _con.push_back(x); //再将该数据通过向上调整算法使得数组保持堆的结构 AdjustUp(_con.size() - 1); } //获取数据(队头数据) const T& top() { return _con[0]; } //删除数据(出优先级队列) void pop() { //首先首尾数据进行交换,将堆顶数据进行尾删 swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); //再将首位置数据通过向下调整算法使得数组保持堆的结构 AdjustDown(0); } //获取队列数据个数 size_t size() { return _con.size(); } //判空 bool empty() { return _con.size() == 0; } private: Container _con; }; }

    2.4 测试代码演示

//Test.cpp //测试priority_queue模拟实现 void test_priority_queue() { MyPriorityQueue::priority_queue<int> pq1; pq1.push(4); pq1.push(7); pq1.push(2); pq1.push(5); pq1.push(10); pq1.push(8); while (!pq1.empty()) { cout << pq1.top() << " "; pq1.pop(); } cout << endl; MyPriorityQueue::priority_queue<int, vector<int>, Greater<int>> pq2; pq2.push(4); pq2.push(7); pq2.push(2); pq2.push(5); pq2.push(10); pq2.push(8); while (!pq2.empty()) { cout << pq2.top() << " "; pq2.pop(); } cout << endl; } int main() { test_priority_queue(); return 0; }

三、OJ实战练习:数组中第k个最大的元素

题目链接:

215. 数组中的第K个最大元素 - 力扣(LeetCode)

题目描述:

C++算法代码:

class Solution { public: int findKthLargest(vector<int>& nums, int k) { //默认构造 + push // priority_queue<int> pq; // for(auto e : nums) // { // pq.push(e); // } //迭代器区间构造(更简洁) priority_queue<int> pq(nums.begin(), nums.end()); while(--k) { pq.pop(); } return pq.top(); } };
结束语

      到此,优先级队列也就讲解完了。作为 STL 中基于堆结构实现的容器适配器,priority_queue 用简洁的接口封装了复杂的堆调整逻辑,无论是默认的大堆排序,还是自定义类型的优先级适配,都能轻松应对 “按优先级取元素” 的需求。优先级队列功能的模拟实现本身是没有难度的,因为底层结构是基于之前所学的数据结构堆,本篇文章主要是对仿函数这个以前没有接触过在使用上像函数的类进行讲解。希望这篇文章对大家学习C++能有所帮助!

C++参考文档:
https://legacy.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
https://en.cppreference.com/w/

Read more

【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk
基于腾讯云HAI + DeepSeek快速设计自己的个人网页

基于腾讯云HAI + DeepSeek快速设计自己的个人网页

前言:通过结合腾讯云HAI 强大的云端运算能力与DeepSeek先进的 AI技术,本文介绍高效、便捷且低成本的设计一个自己的个人网页。你将了解到如何轻松绕过常见的技术阻碍,在腾讯云HAI平台上快速部署DeepSeek模型,仅需简单几步,就能获取一个包含个人简介、技能特长、项目经历及联系方式等核心板块的响应式网页。 目录 一、DeepSeek模型部署在腾讯云HAI 二、设计个人网页 一、DeepSeek模型部署在腾讯云HAI 把 DeepSeek 模型部署于腾讯云 HAI,用户便能避开官网访问限制,直接依托腾讯云 HAI 的超强算力运行 DeepSeek-R1 等模型。这一举措不仅降低了技术门槛,还缩短了部署时间,削减了成本。尤为关键的是,凭借 HAI 平台灵活且可扩展的特性,用户能够依据自身特定需求定制专属解决方案,进而更出色地适配特定业务场景,满足各类技术要求 。 点击访问腾讯云HAI控制台地址: 算力管理 - 高性能应用服务 - 控制台 腾讯云高性能应用服务HAI已支持DeepSeek-R1模型预装环境和CPU算力,只需简单的几步就能调用DeepSeek - R1

By Ne0inhk
AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

云边有个稻草人-ZEEKLOG博客 目录 引言 一、什么是DeepSeek? 1.1 DeepSeek平台概述 1.2 DeepSeek的核心功能与技术 二、蓝耘通义万相2.1概述 2.1 蓝耘科技简介 2.2 蓝耘通义万相2.1的功能与优势 1. 全链条智能化解决方案 2. 强大的数据处理能力 3. 高效的模型训练与优化 4. 自动化推理与部署 5. 行业专用解决方案 三、蓝耘通义万相2.1与DeepSeek的对比分析 3.1 核心区别 3.2 结合使用的优势 四、蓝耘注册流程 五、DeepSeek与蓝耘通义万相2.1的集成应用 5.1 集成应用场景 1. 智能医疗诊断

By Ne0inhk
如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

它是免费的——社区驱动的人工智能💪。         当 OpenAI 第一次推出定制 GPT 时,我就明白会有越来越多的人为人工智能做出贡献,并且迟早它会完全由社区驱动。         但从来没有想过它会如此接近😂让我们看看如何在 Windows 机器上完全免费使用第一个开源推理模型!  步骤 0:安装 Docker 桌面         我确信很多人已经安装了它,所以可以跳过,但如果没有 — — 这很简单,只需访问Docker 的官方网站,下载并运行安装 👍         如果您需要一些特定的设置,例如使用 WSL,那么有很多指导视频,请查看!我将继续下一步。 步骤 1:安装 CUDA 以获得 GPU 支持         如果您想使用 Nvidia 显卡运行 LLM,则必须安装 CUDA 驱动程序。(嗯……是的,它们需要大量的计算能力)         打开CUDA 下载页面,

By Ne0inhk