【C++】优先队列的使用及模拟实现

💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

导读

一、什么是优先队列

二、优先队列的使用

1. 优先队列的构造

2. 优先队列的基本操作

3. 使用示例

三、优先队列模拟实现

1. 仿函数

2. 成员变量

3. 向上调整

4. push函数

5. 向下调整

6. pop函数

7. empty和size

8. top函数

四、完整代码

1. p_queue.h

2. test.cpp


导读

我们上次学习了栈和队列,今天我们来学习优先队列,主要是了解它的一些基本使用和如何模拟实现。

一、什么是优先队列

优先队列(Priority Queue)是一种高效的数据结构,它是队列的一种扩展,不同之处在于每个元素都有一个相关的优先级。在优先队列中,元素不是按照插入的顺序进行排列,而是按照元素的优先级进行排列,优先级高的元素排在前面。

优先队列的定义可以有多种方式,其中一种常见的定义方式是基于堆(Heap)数据结构实现的。堆是一种二叉树,满足以下两个条件:

  1. 堆的根节点是最小或最大元素,即满足最小堆或最大堆的性质。
  2. 堆的每个节点的值都小于或大于其子节点的值,即满足堆的有序性。

基于堆实现的优先队列可以使用数组或链表来表示堆结构,并提供一些基本的操作,如插入元素、删除优先级最高的元素等。插入元素时,根据元素的优先级,将元素插入到合适的位置;删除元素时,取出优先级最高的元素,并保持堆的有序性。

不同于普通队列的FIFO(先进先出)特性,优先队列的元素按照优先级进行排序,具有最高优先级的元素会被最先处理。

特点:

  1. 元素具有优先级:与普通队列不同,优先队列中的元素具有优先级。每个元素都被赋予一个优先级值,用来确定其在队列中的位置。

  2. 按优先级排序:优先队列中的元素按照优先级进行排序。具有最高优先级的元素会被放在队列的最前面。

  3. 自动排序:在插入元素时,优先队列会自动根据元素的优先级进行排序。较高优先级的元素会被排在前面,较低优先级的元素会被排在后面。

  4. 快速访问最高优先级元素:优先队列支持快速访问具有最高优先级的元素。可以通过获取队列的顶部元素来获得具有最高优先级的元素。

  5. 插入和删除操作的时间复杂度:在堆实现的优先队列中,插入和删除元素的平均时间复杂度为O(log n),其中n是当前队列中的元素个数。

需要注意的是,优先队列并不保证相同优先级的元素的顺序,它只保证具有较高优先级的元素会被优先处理。如果需要保持相同优先级元素的顺序,可以通过自定义比较函数来实现。

二、优先队列的使用

1. 优先队列的构造

默认情况下,priority_queue使用less<int>作为比较器,以使元素按降序排列。也就是大堆。

如果要使用其他比较器或自定义排序规则,可以在创建优先队列对象时传递一个比较器函数对象或lambda表达式。

例如,要使元素按升序排列,可以使用以下代码:

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

优先队列的构造方式有以下几种:

  1. 默认构造:使用无参构造函数创建一个空的优先队列,例如:priority_queue<int> pq;

  2. 指定容器构造:可以通过指定容器类型和容器对象来构造优先队列。例如,使用vector容器构造一个最大堆优先队列:priority_queue<int, vector<int>> pq;,使用现有的vector对象构造优先队列:vector<int> vec; priority_queue<int, vector<int>> pq(vec);

  3. 指定比较函数构造:可以通过指定一个自定义的比较函数来构造优先队列,以改变默认的元素优先级比较规则。比较函数可以是自定义的函数、函数指针或者函数对象。例如,构造一个从小到大排序的优先队列:priority_queue<int, vector<int>, greater<int>> pq;,构造一个使用自定义比较函数的优先队列:auto cmp = [](int a, int b){ return a % 10 < b % 10; }; priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);

2. 优先队列的基本操作

优先队列的基本操作包括插入元素、删除最高优先级元素、判断队列是否为空等。常用的操作函数有:

  • push(element):将元素插入到优先队列中。
  • pop():移除优先队列中的最高优先级元素。
  • top():返回优先队列中的最高优先级元素。
  • empty():判断优先队列是否为空,如果为空返回true,否则返回false
  • size():返回优先队列中元素的个数。

3. 使用示例

1. 包含头文件:首先需要包含<queue>头文件

#include <queue>

2. 定义优先队列:使用priority_queue类定义一个优先队列对象(默认大堆)

priority_queue<int> pq;

 3. 插入元素:使用push()函数向优先队列中插入元素

pq.push(5);  // 插入元素5
pq.push(2);  // 插入元素2
pq.push(8);  // 插入元素8

4. 访问元素:可以使用top()函数获取具有最高优先级的元素

int highestPriority = pq.top();  // 获取最高优先级元素

 5. 删除元素:使用pop()函数从优先队列中删除具有最高优先级的元素

pq.pop();  // 删除最高优先级元素

完整示例: 

#include <iostream>
#include <queue>
using namespace std;int main() 
{priority_queue<int> pq;pq.push(5);pq.push(2);pq.push(8);int highestPriority = pq.top();cout << "The highest priority element is: " << highestPriority << endl;pq.pop();highestPriority = pq.top();cout << "The new highest priority element is: " << highestPriority << endl;return 0;
}

三、优先队列模拟实现

1. 仿函数

因为我们下面的模拟实现要用到这个知识,所以我们先来了解一下。

仿函数(Functor)是一种重载了圆括号运算符 operator() 的类对象,使其具有函数的行为。通过重载圆括号运算符,我们可以像调用函数一样使用这个类对象来执行某些操作。

仿函数可以将函数调用的语义封装在类对象的操作中,从而具有更多的灵活性和可定制性。它可以接受参数,执行特定的操作,并返回一个结果。

使用仿函数的好处是可以将其作为参数传递给其他函数或容器类(如sorttransformfind_if等),从而实现对容器中的元素进行自定义的操作和排序。

	template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};
  1. less 类实现了一个比较操作符函数 operator(),用于比较两个元素的大小。它接受两个常引用参数 x 和 y,并返回一个布尔值。在该实现中,它通过 < 运算符来比较 x 和 y 的大小,如果 x 小于 y,则返回 true,否则返回 false

  2. greater 类与 less 类类似,也实现了一个比较操作符函数 operator(),用于比较两个元素的大小。同样,它接受两个常引用参数 x 和 y,并返回一个布尔值。不同的是,它通过 > 运算符来比较 x 和 y 的大小,如果 x 大于 y,则返回 true,否则返回 false

这两个仿函数类可以在定义优先队列时作为比较准则的类型参数进行使用,用于指定元素之间的比较规则。

2. 成员变量

下述代码是在C++中定义优先队列(priority_queue)的模板类。优先队列是一种基于堆(heap)的数据结构,它的特点是每次取出的元素都是当前优先级最高的元素。

在模板类的定义中,有三个模板参数:

  1. T:表示元素的类型。
  2. Container:表示底层容器的类型,默认为vector<T>。优先队列使用底层容器来存储元素,可以根据需要选择不同类型的容器。
  3. Compare:表示元素之间的比较准则,默认为less<T>。比较准则是一个仿函数(应用上述提到的仿函数的概念),它定义了如何比较两个元素的优先级。默认情况下,使用less<T>来进行比较,即优先级高的元素被认为是小的元素。

在模板类的私有部分,定义了一个私有变量_con,它是容器类型Container的一个对象,用于存储优先队列的元素。

    template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{private:Container _con;};

3. 向上调整

		void adjust_up(size_t child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}

上述代码是一个成员函数 adjust_up 的实现,用于调整优先队列中某个节点的位置以保持堆的性质。

在该函数中,首先创建一个 Compare 对象 com,用于进行元素的比较。

然后,根据子节点的索引 child 计算出其父节点的索引,并使用循环进行调整。

在每次循环中,比较父节点和子节点的值,如果父节点的值比子节点的值小(通过调用 com 的 operator() 来比较),则交换两者的位置,并更新子节点和父节点的索引。这样,每次循环都会将较大的值上移一层,直到子节点的值不大于父节点的值或者子节点已经到达根节点。

最终,该函数保证经过调整后,优先队列中的元素按照 Compare 对象 com 所定义的比较准则(通过调用 com 的 operator())保持了堆的性质。

4. push函数

		void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}

首先,将元素 x 插入到容器 _con 的末尾,使用 push_back 函数。

然后,调用 adjust_up 函数对插入的元素进行上调操作。_con.size() - 1 表示插入元素的索引,即最后一个元素的索引。

adjust_up 函数的作用是将插入的元素与其父节点进行比较并交换,以保持堆的性质。

通过这样的操作,插入的元素将逐步上移,直到满足堆的性质为止。

5. 向下调整

		void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){++child;}if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}

以上代码是一个成员函数 adjust_down 的实现,用于在删除元素或进行堆化操作时,将某个节点与其子节点进行比较并交换,以保持堆的性质。

函数接收一个参数 parent,表示要进行调整的节点的索引。

首先,创建一个 Compare 对象 com,用于指定比较准则。

然后,根据节点的索引 parent 计算其左子节点的索引 child,即 child = parent * 2 + 1

接下来,使用一个循环,不断比较父节点和子节点的值,如果满足交换条件,则进行交换操作。

具体的交换条件如下:

  1. 如果右子节点存在且右子节点的值大于左子节点的值,将 child 增加 1,即指向右子节点。
  2. 如果父节点的值小于子节点的值,进行交换操作,即将父节点和子节点的值互换。
  3. 更新父节点和子节点的索引,父节点变为交换后的子节点,子节点变为新的左子节点。

最后,如果不满足交换条件,则退出循环。

通过这样的操作,被调整的节点将逐渐下移,直到满足堆的性质为止。

6. pop函数

		void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}

首先,使用 swap 函数将堆顶元素 _con[0] 与最后一个元素 _con[_con.size() - 1] 进行交换。

然后,使用 pop_back 函数将最后一个元素删除。

最后,调用 adjust_down 函数对堆顶元素进行调整,以保持堆的性质。

7. empty和size

		bool empty(){return _con.empty();}size_t size(){return _con.size();}

empty 函数用于判断堆是否为空,通过调用 _con.empty() 来判断内部容器 _con 是否为空,如果为空则返回 true,否则返回 false

size 函数用于返回堆中元素的个数,通过调用 _con.size() 来获取内部容器 _con 的大小,即堆中元素的个数,并将其返回。

8. top函数

		const T& top(){return _con[0];}

top 函数用于返回堆顶的元素,即堆中最大(或最小)的元素。在该实现中,直接通过 _con[0] 获取堆顶的元素,并将其返回。注意此处返回的是一个常引用 const T&,表示返回的元素不能被修改。

四、完整代码

1. p_queue.h

#pragma once
#include <iostream>#include <vector>
#include<algorithm>
using namespace std;namespace Myq_queue
{template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{public:void adjust_up(size_t child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){++child;}if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}bool empty(){return _con.empty();}size_t size(){return _con.size();}const T& top(){return _con[0];}private:Container _con;};
}

2. test.cpp

void test_priority_queue()
{// 小堆Myq_queue::priority_queue<int, vector<int>, Myq_queue::greater<int>> pq;pq.push(2);pq.push(1);pq.push(4);pq.push(3);pq.push(7);pq.push(8);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;}
int main()
{test_priority_queue();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/32211.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【硬件开发】共模电感

为什么电源无论直流还是交流的输入端都需要一个共模电感 图中L1就是共模电感&#xff0c;长下面这个样子&#xff0c;两侧的匝数&#xff0c;线径和材料都是一模一样的 共模电感的作用是为了抑制共模信号 抑制共模信号工作原理 http://【共模电感是如何抑制共模信号的】https…

【免费】中国电子学会2024年03月份青少年软件编程Python等级考试试卷一级真题(含答案)

2024-03 Python一级真题 分数&#xff1a;100 题数&#xff1a;37 测试时长&#xff1a;60min 一、单选题(共25题&#xff0c;共50分) 1. 下列哪个命令&#xff0c;可以将2024转换成2024 呢&#xff1f;&#xff08; A&#xff09;(2分) A.str(2024) B.int(2024) C.fl…

细说AGV的12种导航方式和原理

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 这十二种导航方式各自具有不同的特点和应用场景&#xff0c;下面我将逐一进行简要介绍&#xff1a; 磁钉导航&#xff1a; 原理&#xf…

Python学习笔记17:进阶篇(六)代码测试

代码测试 代码测试是软件开发过程中的关键环节&#xff0c;旨在确保代码质量、功能正确性以及性能符合预期。 在开发过程中&#xff0c;进行代码测试有很多好处&#xff1a; 提高软件质量&#xff1a;通过发现并修复错误&#xff0c;测试有助于提升软件的功能性、可靠性和稳…

LSTM架构的演进:LSTM、xLSTM、LSTM+Transformer

文章目录 1. LSTM2. xLSTM2.1 理论介绍2.2 代码实现 3. LSTMTransformer 1. LSTM 传统的 LSTM (长短期记忆网络) 的计算公式涉及几个关键部分&#xff1a;输入门、遗忘门、输出门和单元状态。 2. xLSTM xLSTM之所以称之为xLSTM就是因为它将LSTM扩展为多个LSTM的变体&#xff…

网络编程--网络理论基础(二)

这里写目录标题 网络通信流程mac地址、ip地址arp协议交换机路由器简介子网划分网关 路由总结 为什么ip相同的主机在与同一个互联网服务通信时不冲突公网ip对于同一个路由器下的不同设备&#xff0c;虽然ip不冲突&#xff0c;但是因为都是由路由器的公网ip转发通信&#xff0c;接…

主流中间件--Redis

NOSQL 什么是NOSQL NoSQL(NoSQL Not Only SQL )&#xff0c;意即“不仅仅是SQL”&#xff0c;它泛指非关系型的数据库。 关系型数据库&#xff1a;以关系(由行和列组成的二维表)模型建模的数据库。简单理解&#xff1a;有表的就是关系型数据库。 NOSQL分类 Redis 什么是Redi…

内容安全复习 7 - 对抗攻击与防御

文章目录 概述攻击对抗性攻击的目的攻击的损失函数如何攻击FGSM黑盒与白盒真实世界的攻击 防御被动防御主动防御 概述 动机 &#xff08;1&#xff09;不仅要在实验室中部署机器学习分类器&#xff0c;也要在现实世界中部署&#xff1b;实际应用 &#xff08;2&#xff09;分类…

【数据结构】线性表之《栈》超详细实现

栈 一.栈的概念及结构二.顺序栈与链栈1.顺序栈2.链栈1.单链表栈2.双链表栈 三.顺序栈的实现1.栈的初始化2.检查栈的容量3.入栈4.出栈5.获取栈顶元素6.栈的大小7.栈的判空8.栈的清空9.栈的销毁 四.模块化源代码1.Stack.h2.Stack.c3.test.c 一.栈的概念及结构 栈&#xff1a;一种…

程序猿成长之路之数据挖掘篇——决策树分类算法(1)——信息熵和信息增益

决策树不仅在人工智能领域发挥着他的作用&#xff0c;而且在数据挖掘中也在分类领域中独占鳌头。了解决策树的思想是学习数据挖掘中的分类算法的关键&#xff0c;也是学习分类算法的基础。 什么是决策树 用术语来说&#xff0c;决策树&#xff08;Decision Tree&#xff09;是…

Go自定义数据的序列化流程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

数据库设计概述-数据库设计内容、数据库设计方法(基于E-R模型的规范设计方法)

一、引言 如何利用关系数据库理论设计一个满足应用系统需求的数据库 二、数据库设计内容 1、数据库设计是基于应用系统需求分析中对数据的需求&#xff0c;解决数据的抽象、数据的表达和数据的存储结构等问题 2、其目标是设计出一个满足应用要求、简洁、高效、规范合理的数…

Map集合之HashMap细说

最近在看面试题&#xff0c;看到了hashmap相关的知识&#xff0c;面试中问的也挺多的&#xff0c;然后我这里记录下来&#xff0c;供大家学习。 Hashmap为什么线程不安全 jdk 1.7中&#xff0c;在扩容的时候因为使用头插法导致链表需要倒转&#xff0c;从而可能出现循环链表问…

航行在水域:使用数据湖构建生产级 RAG 应用程序

在 2024 年年中&#xff0c;创建一个令人印象深刻和兴奋的 AI 演示可能很容易。需要一个强大的开发人员&#xff0c;一些聪明的提示实验&#xff0c;以及一些对强大基础模型的API调用&#xff0c;你通常可以在一个下午建立一个定制的AI机器人。添加一个像 langchain 或 llamain…

c++ 内存分析模型、引用

一、内存模型分区 内存四区的意义&#xff1a; 不同区域存放的数据&#xff0c;赋予不同的生命周期&#xff0c;给我们更大的灵活编程 &#xff08;一&#xff09;程序运行前 在程序编译后&#xff0c;生成了exe可执行程序&#xff0c;未执行程序前分为两个区域 代码区&…

SpringMVC系列七: 手动实现SpringMVC底层机制-上

手动实现SpringMVC底层机制 博客的技术栈分析 &#x1f6e0;️具体实现细节总结 &#x1f41f;准备工作&#x1f34d;搭建SpringMVC底层机制开发环境 实现任务阶段一&#x1f34d;开发ZzwDispatcherServlet&#x1f966;说明: 编写ZzwDispatcherServlet充当原生的DispatcherSer…

码云建仓库

1.新建仓库 码云地址 打开 码云地址 &#xff0c;点击“”&#xff0c;新建仓库&#xff0c;添加仓库内容 &#xff0c;创建。 小提示&#xff1a;如果本地已有项目&#xff0c;就不要选初始化&#xff0c;设置模板&#xff0c;容易冲突。 2. 进入当前仓库页 小提示&#x…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的巡演(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

心明眼亮 洞悉万物

如何洞悉事物的本质呢&#xff1f; 阳明先生&#xff1a;世间之事&#xff0c;纷繁复杂&#xff0c;不可能一一研究得过来。 圣人只需要把内心的明镜擦亮&#xff0c;而无需担心外部的事事物物在镜子中如何映照。 —— 外界事物是无穷无尽的&#xff0c;永远探究不完&#xf…

30 - 每位经理的下属员工数量(高频 SQL 50 题基础版)

30 - 每位经理的下属员工数量 -- 根据reports_to &#xff0c;获取employee_id,即分组用e1.reports_to&#xff0c;查询用e2.employee_id,e2.nameselect e2.employee_id,e2.name ,count(e1.reports_to) reports_count,round(avg(e1.age),0) average_age from Employees e1 left…