【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…

【LLVM】‘ffast-math’ and ‘ffp-contract’

最近看到一个issue&#xff0c;修改的核心代码部分并不多&#xff0c;可以参考此处的介绍以及此处的issue。 看起来关键就是判断-ffp-contract会将contract的值设为最后一个此选项的值&#xff0c;否则的话&#xff0c;如果只指定了-ffast-math但是没有通过-ffp-contract设置值…

fffdddd

library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all;entity GJL isport(clk, reset: in std_logic;btn_green, btn_red: in std_logic; -- 新增控制按键r1, r2, y1, y2, g1, g2: out std_logic;ledag: out std_logic_…

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

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

在Java中使用Apache Kafka进行消息队列处理

在Java中使用Apache Kafka进行消息队列处理 消息队列(Message Queue)是分布式系统中用于异步通信的关键组件,广泛应用于解耦生产者和消费者、平滑流量突增、提高系统弹性等场景。Apache Kafka作为一个高吞吐量、分布式的消息队列系统,已经成为许多企业的首选。本文将介绍如…

Linux 进程管理指令

Linux 进程管理是系统管理的重要部分&#xff0c;通过各种工具和命令&#xff0c;你可以查看、控制、调试和管理进程。以下是一些常用的 Linux 进程管理命令和工具。 查看进程 1. ps ps 命令用于列出当前系统的进程。 查看当前用户的所有进程&#xff1a; ps -u $USER查看…

Python statistics 模块

Python 的 statistics 模块提供了一组用于执行各种统计计算的函数&#xff0c;包括平均值、中位数、标准差、方差以及其他统计量。让我来简单介绍一下。 首先&#xff0c;你可以使用以下方式导入 statistics 模块&#xff1a; python import statistics 接下来&#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;分类…

shell jq教程

1. jq 简介 jq 是一款命令行下处理JSON数据的工具。其可以接受标准输入&#xff0c;命令管道或者文件中的JSON数据&#xff0c;经过一系列的过滤器(filters)和表达式的转后形成我们需要的数据结构并将结果输出到标准输出中。jq的这种特性使我们可以很容易地在Shell脚本中调用它…

LeetCode 23. 合并 K 个升序链表

更多题解尽在 https://sugar.matrixlab.dev/algorithm 每日更新。 组队打卡&#xff0c;更多解法等你一起来参与哦&#xff01; LeetCode 23. 合并 K 个升序链表&#xff0c;难度困难。 优先队列&#xff08;小顶堆&#xff09; 解题思路&#xff1a;拿到题首先想到以下几个方…

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

栈 一.栈的概念及结构二.顺序栈与链栈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;从而可能出现循环链表问…