【C++】STL中优先级队列的使用与模拟实现

前言:在前面我们学习了栈和队列的使用与模拟实现,今天我们来进一步的学习优先级队列使用与模拟实现

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • 什么是优先级队列
    • 优先级的队列常见功能的使用方式
    • 什么是仿函数
  • 优先级队列的底层实现(适配器版本)
    • 优先级队列的基本框架
      • push():将元素入队列
      • pop():删除队列中优先级最高的元素
      • top():获取并返回队列中优先级最高的元素
      • size():获取并返回队列大小
      • empty():判断队列是否为空
    • 整体代码


什么是优先级队列

C++中的优先级队列是一种特殊的数据结构,它类似于队列,但是元素按照优先级进行排序。在优先级队列中,元素的插入被赋予了一个优先级值,具有较高优先级的元素将排在较低优先级的元素之前(用大白话讲,就是你队列中的元素按照某种要求进行了对应的排序)。

C++中的优先级队列通常使用堆(heap)作为底层实现,可以是最小堆或最大堆。最小堆意味着优先级值较小的元素具有较高的优先级,而最大堆则相反。

优先级队列的主要操作是插入和删除最高优先级的元素。在C++中,可以使用std::priority_queue模板类来实现优先级队列。该模板类提供了一些成员函数,如push()、pop()和top()等,用于插入、删除和获取最高优先级的元素。


优先级的队列常见功能的使用方式

1.插入元素:使用push()函数向优先级队列插入元素(调用前需要导入头文件queue)

int main()
{priority_queue<int> s1;//创建一个对象s1.push(10);//入队列,且队列中的元素会默认按照降序进行排序s1.push(20);s1.push(0);s1.push(9);s1.push(120);return 0;
}

  1. 删除最高优先级元素:使用pop()函数删除优先级队列中的最高优先级元素(可以理解成删除队列中排序过后的队头的元素)。
int main()
{priority_queue<int> s1;s1.push(10);//入队列s1.push(120);s1.pop();//删除队头的元素return 0;
}

  1. 获取最高优先级元素:使用top()函数获取优先级队列中的最高优先级元素(通俗的理解获取队列排序过后的队头元素)。
int main()
{priority_queue<int> s1;s1.push(10);//入队列s1.push(9);s1.push(120);s1.pop();//出队列auto num = s1.top();//获取队列的队头的元素cout << num << endl;return 0;
}
//运行结果:10

  1. 获取队列中的元素数量:使用size()函数获取优先级队列中的元素数量。
int main()
{priority_queue<int> s1;s1.push(10);//入队列cout << s1.size() << endl;//输出队列中元素的个数return 0;
}

  1. 判断队列是否为空:使用empty()函数判断优先级队列是否为空。
int main()
{priority_queue<int> s1;s1.push(10);s1.push(20);s1.push(0);s1.push(9);s1.push(120);auto num = s1.top();cout << num << endl;cout << s1.size() << endl;if (!s1.empty())//判断队列是否有元素{cout << "队列中有元素" << endl;}else cout << "队列中没有元素" << endl;return 0;
}

需要注意的是,std::priority_queue默认以降序排序,即最大元素具有最高优先级。如果希望以升序排序,则可以使用自定义的比较函数或者使用std::greater作为模板参数

// 使用自定义的比较函数,greater即默认升序的排序方式,也可以调用自己写的排序方式
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;// 或者使用std::greater模板参数
std::priority_queue<int, std::vector<int>, std::greater<>> pq;


什么是仿函数

C++中的仿函数是一个类或者结构体,实现了函数调用操作符(operator())的重载。通过重载函数调用操作符,可以使得仿函数对象像函数一样被调用。仿函数常用于算法中,用于实现特定的操作或者运算。它可以接受一个或多个参数,并返回一个结果。通过仿函数,可以实现函数对象的状态的维护,以及在算法中对元素进行处理的定制化操作。
例子:

template <class T>
class less//通过类来判断他的大小
{
public:operator() (const T& x, const T& y){return x < y;}
};template <class T>
class greater//同理
{
public:operator() (const T& x, const T& y){return x > y;}
};int main()
{int num1 = 10, num2 = 20,num3 = 30,num4 = 50;less<int> s1;greater<int> s2;if (s1(num1, num2))//s1()就类似与之前的一个函数的样子,我们调用s1这个对象即可完成你想要指定的操作{cout << "num1 < num2" << endl;}if (s2(num4, num3))//同理你调用s2也可以类似于函数的感觉,去完成你想要指定完成的操作{cout << "num4 > num3" << endl;}return 0;
}

优先级队列的底层实现(适配器版本)

前言:这里我们需要先知道刚刚我们使用了那些功能,这里方便我们对其进行模拟实现。
基本操作:
1、push():将元素入队列。【将元素入队列,并且让其按照升序或者降序进行某种方式进行排序】

2、pop():删除队列中优先级最高的元素。【队首出队列,并且依然要保持队列的完整性】

3、top():获取并返回队列中优先级最高的元素。

4、size():获取并返回队列大小。【返回值为 unsigned int(size_t)类型】

5、empty():判断队列是否为空。【返回值为bool类型。队列为空返回true,不空返回false】


优先级队列的基本框架

namespace bit
{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:private:Container _con;//调用适配器}
}

push():将元素入队列

代码思路:我们的适配器默认调用的是vector因为他内部的接口可以调用尾插,同理我们这里直接调用尾插的函数即可,但是我们提到过这个是一个优先级队列,优先级队列的底层就是一个,那么我们对堆插入元素的时候在数据结构讲过我们需要对其进行调整,否则这个堆就会失去他原有的完整性,如果对堆这块内容不太熟悉的小伙伴可以去看看我之前的博客堆的模拟实现

void push(const T& x)//入队列
{_con.push_back(x);//需要保证队列里面的优先级的顺序,因此我们需要对其进行向上调整adjust_up(_con.size() - 1);//当前的元素个数减一就是当前孩子结点下标,然后将他向上调整即可完成插入
}//向上调整
void adjust_up(size_t child)//插入的是孩子看孩子是否比父亲还大,比父亲还大就交换,且是默认大堆
{Compare com;int parent = (child - 1) / 2;//父亲结点的下标while (child > 0){//巧妙的利用仿函数对其进行默认的降序进行排序,也可以通过传great进行升序排序if (com(_con[parent], _con[child]))//比父亲大就交换{swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;//孩子不比父亲大,说明就是当前的位置}}
}

pop():删除队列中优先级最高的元素

代码思路:在之前讲解堆的时候我们知道,要想删除堆顶的元素,我们通常的做法是将堆顶的元素和尾元素进行交换,然后在删除此时堆尾部的元素,进行删除,然后在将交换后的堆顶的元素向下调整保持堆的完整性,因此我们在优先级队列中需要用到同样的做法,将队首和队尾的元素进行交换,然后删除队尾的元素,然后向下调整即可。

void pop()//队头出队列
{swap(_con[0], _con[_con.size() - 1]);//将堆的底部元素和堆顶的交换,然后删除堆底部元素就完成了队头出队列_con.pop_back();//删除堆顶元素adjust_down(0);//向下调整交换过后的堆,即依然保持堆的完整性
}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[parent], _con[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}

top():获取并返回队列中优先级最高的元素

代码思路:我们知道优先级队列的底层就是个堆,队首的那个元素就是堆顶的元素,因此直接返回下标为0的首元素即可。

const T& top()//获取队首元素
{return _con[0];
}

size():获取并返回队列大小

代码思路:这里我们直接调用容器中的函数即可

size_t size()//查看队列中元素的个数
{return _con.size();
}

empty():判断队列是否为空

代码思路:同理调用容器中的函数即可。

bool empty()//判断是否为空,依然玩适配器的那套玩法
{return _con.empty();
}

整体代码

#include <iostream>
#include<vector>
#include<assert.h>
using namespace std;namespace bit
{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[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;//孩子不比父亲大,说明就是当前的位置}}}void push(const T& x)//入队列{_con.push_back(x);adjust_up(_con.size() - 1);//vector的元素个数减一就是当前孩子结点下标,然后将他向上调整即可完成插入}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[parent], _con[child]);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;};void test1(){bit::priority_queue<int, vector<int>, bit::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;}void test2(){priority_queue<int> pq;//默认大堆pq.push(2);pq.push(1);pq.push(4);pq.push(3);pq.push(7);pq.push(8);while (pq.size()){cout << pq.top() << " ";pq.pop();}cout << endl;}
}emplate <class T>
class less
{
public:operator() (const T& x, const T& y){return x < y;}
};template <class T>
class greater
{
public:operator() (const T& x, const T& y){return x > y;}
};int main()
{int num1 = 10, num2 = 20,num3 = 30,num4 = 50;less<int> s1;greater<int> s2;if (s1(num1, num2)){cout << "num1 < num2" << endl;}if (s2(num4, num3)){cout << "num4 > num3" << endl;}return 0;
}//int main()
//{
//	//bit::test1();
//	//bit::test2();
//	return 0;
//}

好啦,今天的内容就到这里啦,下期内容预告C++中的模板大全解.


结语:前段时间博主又又又去忙学校的事情和一些比赛啥的,这段时间会猛猛干的!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

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

相关文章

常见的8种排序(含代码):插入排序、冒泡排序、希尔排序、快速排序、简单选择排序、归并排序、堆排序、基数排序

时间复杂度O(n^2) 1、插入排序 (Insertion Sort) 从第一个元素开始&#xff0c;该元素可以认为已经被排序&#xff1b;取出下一个元素&#xff0c;在已经排序的元素序列中从后向前扫描&#xff1b;如果该元素&#xff08;已排序&#xff09;大于新元素&#xff0c;将该元素移到…

IPv6 address status lifetime

IPv6 地址状态转换 Address lifetime (地址生存期) 每个配置的 IPv6 单播地址都有一个生存期设置&#xff0c;该设置确定该地址在必须刷新或替换之前可以使用多长时间。某些地址设置为“永久”并且不会过期。“首选”和“有效”生存期用于指定其使用期限和可用性。 自动配置的…

vue中的状态管理

第1部分&#xff1a;引言 状态管理是应用中数据流动和变更的核心机制。在Vue应用中&#xff0c;状态管理不仅涉及到组件间的数据共享&#xff0c;还包括了数据的持久化、异步操作的处理等复杂场景。良好的状态管理策略可以提高应用的响应速度&#xff0c;降低组件间的耦合度&a…

spring使用redis作为消息订阅

redis可以用于消息订阅&#xff0c;下面一段代码实现了spring中使用redis作为消息订阅模型&#xff1a; 消息处理器&#xff1a; /*** 接口RedisMessageHandler:*/ public interface RedisMessageHandler {/*** 处理redis消息* param message redis消息* return 处理结果*/Str…

Electron录制-webm转mp4时长拉长问题

问题1 在导出视频时&#xff0c;发现最终导出视频的时长与实际录制的视频时长不一样&#xff0c;而且视频中某一帧会延迟。 猜想0 在项目中使用mp4-wasm导出mp4视频&#xff0c;同时利用canvas做一些效果&#xff0c;猜想是在canvas处理时有延迟。但是当把canvas处理效果全部…

分页查询前端对接

文章目录 添加角色修改角色当点击修改按钮后,那么就会弹出对话框,所以要设置显示为true点击修改的时候就是 要显示对话框 制作用户管理页面开发后端接口用户查询前端整合新增接口功能实现修改 添加角色 首先添加 添加表单的组件 那么总结一下 就是使用 组件 然后再使用变量接…

面试题2:从浏览器输入一个URL,到最终展示前端页面这一过程,会发生什么?

这是一个高频的面试题目。 题目答案是开放性的&#xff0c;一般以后端开发的角度回答。 当地址栏输入一个 URL 后&#xff1a; 一、首先会进行 DNS 域名解析 DNS 域名解析&#xff1a;网络上的设备都是通过 IP 地址&#xff0c;作为身份标识的。但是由于点分十进制的 IP 地址 …

Python基础入门

目录 1. 什么是Python&#xff1f; 2. 安装Python 3. Python基础语法 4. 数据结构 5. 文件操作 6. Python标准库 总结 1. 什么是Python&#xff1f; Python是一种高级编程语言&#xff0c;由Guido van Rossum于1991年发布。它以其简单易读的语法和强大的功能而闻名&…

Nominatim免费的地址解析,逆地址解析,OpenStreetMap开源地图数据【全网最全】

视频学习地址 国内的一些地址解析供应商的API都开始付费了&#xff0c;就想找个免费的地址解析和逆地址解析的应用&#xff0c;最终选择了Nominatim OpenStreetMap 文章目录 一、选型1-1、数据源1-2、地理编码引擎2-1、初尝Nominatim2-1-1、地址解析2-1-2、逆地址解析 2-2、OS…

国内外大模型生态发展报告!

很多同学只知类似Check GPT或者说对国内的一些比较了解&#xff0c;对国外的不太了解&#xff0c;所以在这总结。 1 大模型的发展 左表 名称参数特点发布时间GPT-215亿英文底模&#xff0c;开源2019年Google T5110亿多任务微调, 开源2019年GPT-3.51750亿人工反馈微调2022年M…

UFS Power Mode Change 介绍

一. UFS Power Mode Change简介 1.UFS Power Mode指的是Unipro层的Power State, 也可以称为链路(Link)上的Power Mode, 可以通过配置Unipro Attribute, 然后控制切换Unipro Power State, 当前Power Mode Change有两种触发方式&#xff1a; (1) 通过DME Power Mode Change触发…

手工清理Linux后门:深入分析与实践指南

手工清理Linux后门&#xff1a;深入分析与实践指南 后门概述 后门程序允许未授权用户绕过正常的认证过程&#xff0c;获取对系统的访问权限。攻击者可能会通过修改计划任务、开机启动脚本&#xff0c;甚至植入Rootkit来维持后门。 分析操作系统被动手脚 在清理后门之前&…

创建一个基本的网页爬虫

创建一个基本的网页爬虫通常涉及使用Python库如requests来获取网页内容&#xff0c;以及BeautifulSoup来解析HTML并提取所需的信息。下面是一个简单的Python爬虫示例&#xff0c;该爬虫从网站上抓取新闻标题。为了演示&#xff0c;我将使用一个假设的新闻网站&#xff0c;但你可…

Jvm针对分代垃圾回收算法配置调优

堆大小设置 年轻代的设置很关键 JVM中最大堆大小有三方面限制&#xff1a;相关操作系统的数据模型&#xff08;32-bt还是64-bit&#xff09;限制&#xff1b;系统的可用虚拟内存限制&#xff1b;系统的可用物理内存限制。32位系统下&#xff0c;一般限制在1.5G~2G&#xff1b…

java中实现Callable方式创建线程

一、为啥要引入Callable 在前面讲了通过继承Thread和实现Runnable方式创建线程的区别&#xff0c;那为什么有了Runnable还要引入Callable?下面通过实现Runnable方式的弊端给出答案 实现Runnable方式的弊端&#xff1a; package java.lang; FunctionalInterface public inte…

宁波银行票据案例解读,要注入科技赋能票据新形式

随着科技的飞速发展&#xff0c;金融行业正迎来一场前所未有的变革。作为一家以科技创新为驱动的现代化银行&#xff0c;宁波银行在这场变革中积极探索&#xff0c;宁波银行票据案例之后持续通过引入先进技术&#xff0c;为客户提供更加高效、智能的金融服务。 宁波银行推出的…

1095 解码PAT准考证(测试点3)

solution 测试点3超时&#xff1a;命令为3时&#xff0c;用unordered_map而非map&#xff0c;否则会超时 #include<iostream> #include<string> #include<algorithm> #include<unordered_map> using namespace std; const int maxn 1e4 10; struct…

2024山东大学软件学院创新项目实训(9)使用OpenCompass进行模型评估

下载好OpenCompassData-core-20231110.zip 之后&#xff0c;解压压缩包 unzip OpenCompassData-core-20231110.zip 运行代码&#xff1a; python run.py --datasets ceval_gen --hf-path /hy-tmp/7B21/merged --tokenizer-path /hy-tmp/7B21/merged --tokenizer-kwargs p…

步步精:连接器领域的卓越品牌

自1987年成立以来&#xff0c;步步精坐落于美丽的旅游城市——温州市乐清虹桥镇&#xff0c;被誉为“国家电子主体生产基地”、“国家精密模具制造基地”。公司拥有7大厂区、9大事业部&#xff0c;800名专职员工&#xff0c;致力于提供高品质的连接器解决方案。注册商标“BBJCO…

家庭成员目标管理系统设计

一、项目背景与目标 随着现代社会对家庭教育的重视&#xff0c;家庭成员之间的目标设定与达成成为家庭和谐与进步的关键。本项目旨在设计一个家庭成员目标管理系统&#xff0c;通过系统化的方式帮助家庭成员设定、追踪和达成个人及家庭目标&#xff0c;从而提升家庭成员的成就感…