【C++】-priority_queue(优先级队列的具体使用和模拟实现)以及仿函数的简单介绍

news2024/11/26 7:27:20

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、priority_queue的介绍
  • 二、priority_queue的具体使用
  • 三、模拟实现
    • 3.1priority_queue的框架
    • 3.2构造函数
    • 3.3push和pop
    • 3.4其他功能函数
    • 3.5完整代码
  • 四、仿函数
  • 五、总结


前言

今天我们来学习优先级队列,说他是优先级队列,不如叫他优先级栈,因为他的接口和栈的几乎一样,插入和删除实际都是在栈顶操作,只是在插入和删除的时候进行一些数字的调整,那哦我们一起来看看什么是优先级队列。


一、priority_queue的介绍

在这里插入图片描述
我们看到优先级队列也是一个容易适配器,默认的是vector,一会再说为什么使用vector,仿函数就是为了控制以什么优先方式出队列,

我们再来看看有哪些接口:
在这里插入图片描述
大家看到我一开始为什么说叫做优先级栈了吧,因为接口和栈的几乎一模一样。

我们来看看构造函数:
在这里插入图片描述
我们看到构造函数有两种,一个默认构造,一个是传其他迭代器的首尾进行构造

具体有什么功能我先不说,等具体使用的时候就知道了

二、priority_queue的具体使用

我们来看看这几个接口的功能:

priority_queue<int> pq;
	pq.push(3);
	pq.push(2);
	pq.push(4);
	pq.push(1);
	cout << pq.size() << endl;
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

在这里插入图片描述
我们发现我们插入的时候不是有序的插入,为什么最后是降序的打印出来呢?虽然接口和栈一样,但是功能却是不一样的。

大家思考一件事: 我们的优先级队列默认的底层容器是vector,也就是顺序表的结构,我们想要尾插进去的数字进行排好序,那么最好的办法就是向上调整算法,使他变成一个大/小堆,但是最大/最小的元素在第一个位置,我们的vector没有头删的接口,所以需要把第一个位置和最后一个进行交换,然后删除最后一个元素,使他继续变成大堆/小堆,就是使用向下调整算法,所以有了这个猜想,我们的优先级队列就好比之前的堆排序。

按照刚才的运行结果,我们发现使降序,说明我们的优先级队列默认里面是建大堆的,也说明我们如果使用第二个构造函数进行初始化,就要在构造函数里面进行建堆操作,因为要建堆,所以我们需要大量的使用[],而之前说到deque虽然也能支持随机访问,但是效率没有vector高,所以我们默认使用的vector容器


三、模拟实现

在有了前面的简单说明后,我们就来模拟实现一下,有了之前模拟实现的经验,我们需要自己的命名空间域,然后再模拟实现,不记得建堆的来看这篇博客【数据结构】-堆排序以及时间复杂度的计算

3.1priority_queue的框架

namespace xdh
{
	template<class T,class Container = vector<T>> 
	class Priority_queue
	{
	public:
		Priority_queue(){}//默认构造器
	private:
		Container _c;//容器适配器
	};
}

3.2构造函数

我们主要实现第二个构造函数,我们来看看:

template<class InputIteartor>
		Priority_queue(InputIteartor first, InputIteartor last)
		{
			while (first != last)//先把数据插入到vector里面
			{
				_c.push_back(*first);
				first++;
			}
			for (int i = (_c.size() - 1 - 1) / 2; i >= 0; i--)//通过向下调整算法进行建堆,这个再堆那篇博客说到过
			{
				AdjustDown(i);
			}
		}

3.3push和pop

我们再push的时候要保证整体还是一个堆,就要进行向上调整算法,我们是通过把最后一个元素个堆顶的元素进行交换之后,然后通过向下调整算法使其还是一个堆,我们知道我们再进行向下调整肃反的时候,不能把第一个元素给删除了,然后把数据让前面挪一位,再进行调整,这样效率不高,而且父子结点的下标都乱了,这也刚好说明我们的优先级队列选择了这个没有头删接口的vector

void push(const T&val)
		{
			_c.push_back(val);
			AdjustUp(_c.size() - 1);
		}
		void pop()
		{
			if (_c.empty())
			{
				return;
			}
			swap(_c.front(), _c.back());
			_c.pop_back();
			AdjustDown(0);
		}

为什么要这样建堆,我就不做详细介绍了,参考上面的博客

3.4其他功能函数

		T& top()
		{
			return _c.front();
		}

		size_t size()const
		{
			return _c.size();
		}

		bool empty()const
		{
			return _c.empty();
		}

3.5完整代码

namespace xdh
{
	template<class T,class Container = vector<T>
	class Priority_queue
	{
	public:
		Priority_queue(){}
		template<class InputIteartor>
		Priority_queue(InputIteartor first, InputIteartor last)
		{
			while (first != last)
			{
				_c.push_back(*first);
				first++;
			}
			for (int i = (_c.size() - 1 - 1) / 2; i >= 0; i--)
			{
				AdjustDown(i);
			}
		}
		void push(const T&val)
		{
			_c.push_back(val);
			AdjustUp(_c.size() - 1);
		}
		void pop()
		{
			if (_c.empty())
			{
				return;
			}
			swap(_c.front(), _c.back());
			_c.pop_back();
			AdjustDown(0);
		}

		T& top()
		{
			return _c.front();
		}

		size_t size()const
		{
			return _c.size();
		}

		bool empty()const
		{
			return _c.empty();
		}
	private:
		void AdjustDown(int parent)
		{
			Compare com;
			int child = parent * 2+1;
			while (child<_c.size())
			{
				if (child + 1 < _c.size() && _c[child] ,<_c[child + 1])
				{
					child += 1;
				}
				
				if ( _c[parent]<_c[child] )
				{
					swap(_c[child], _c[parent]);
					parent=child;
					child = parent*2+1;
				}
				else
				{
					break;
				}
					
			}
		}


		void AdjustUp(int child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child >= 0)
			{
				if (_c[parent]< _c[child])
				{
					swap(_c[child], _c[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
	private:
		Container _c;
	};
}

四、仿函数

这里面的我们只能简单的做一个了解,后面再细讲,先带大家入个门,让大家熟悉一下。如果大家看过我们的博客,我在vector的具体使用那一篇中提到仿函数了,哪个时候就说一下,今天我们来实现一下


我们的仿函数也是我们STL的六大组件之一,可以说他和迭代器哪些都是平起平坐,也说明他给我们的使用带来了很大的方便,我们先参考这篇博客C/C++编程:仿函数,这里面说到比较细致,我就针对优先级队列来说一下仿函数怎么使用

我今天降的知识仿函数中的关系运算中的其中两种,一种是less,还有一种是greater,一个小于运算符,一个是大于运算符,通过上面的博客我们知道,我们就是将操作放在一个函数对象里面,通过调用操作去使用,我们上面默认是less,建大堆,其实建什么堆是根据孩子结点和父亲结点的大小去决定的,我们将>或者<封装再一个类里面,然后通过创建对象,去调用不就行。我们来看实现:

template<class T>
struct Less
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

template<class T>
struct Greater
{
public:
	bool operator()(const T& x, const T& y)//操作封装再类里面
	{
		return x > y;
	}
};

然后哪里需要用就创建对象,我们刚才就在向上调整算法和向下调整算法里面用到了比较,我们修改后的算法为:此时我们优先级队列就有了第三个模板参数

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


		void AdjustUp(int child)
		{
			Compare com;//创建函数对象
			int parent = (child - 1) / 2;
			while (child >= 0)
			{
				if (com(_c[parent], _c[child]))//进行调用
				{
					swap(_c[child], _c[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

注意:我们的仿函数里面的比较针对自定义类型的话,自定义类型必须重载我们对应的比较运算符,不然会报错。

注意如果传的是对象的引用,那么比较的时候就要解引用用对象去比较,不能用引用,因为引用时指针,会当成内置类型一样去比较,而且每次运行的指针类型都是不一样的,所以我们每次排出来的都可能使不一样的,所以我们要注意,针对下面的就必须使用下面这种仿函数的写法了

template<class T>
struct LessPNode
{
public:
	bool operator()(const T x, const T y)
	{
		return *x < *y;
	}
};

template<class T>
struct GreaterPNode
{
public:
	bool operator()(const T x, const T y)
	{
		return *x > *y;
	}
};


Priority_queue<Date*,vector<Date*>, GreaterPNode<Date*>> pq;
		pq.push(new Date(2018, 10, 29));
		pq.push(new Date(2018, 5, 28));
		pq.push(new Date(2018, 7, 30));
		while (!pq.empty())
		{
			cout << *(pq.top()) << " ";//因为vector里面存放的使引用是地址,要解引用才能取出来数据
			pq.pop();
		}
		cout << endl;

五、总结

相信大家堆上面的知识应该了解了吧,优先级队列的实现并不难,理解起来我感觉比list简单多了,对于仿函数大家现在知道就行了,以后用到了再说,我们今天的知识就分享到这里了,我们下一篇讲解反向迭代器,因为讲了容器适配器,才能刚好的讲解这个反向迭代器,我们下篇再见
请添加图片描述

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

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

相关文章

ubuntu 开启 ssh 服务 设置root远程登录

设置root用户密码 sudo passwd root安装ssh服务和vim编辑器 sudo apt -y install openssh-server vim开启ssh服务 sudo vim /etc/ssh/ssh_config去掉 配置文件中 Port 22 的注释后保存退出 设置root用户远程登录 sudo vim /etc/ssh/sshd_config将 PermitRootLogin prohibit-pas…

文章审核之敏感词过滤

技术选型 DFA实现原理 DFA全称为&#xff1a;Deterministic Finite Automaton,即确定有穷自动机。 存储&#xff1a;一次性的把所有的敏感词存储到了多个map中&#xff0c;就是下图表示这种结构 敏感词&#xff1a;冰毒、大麻、大坏蛋 工具类 最下面的main方法是测试用的&a…

vuejs源码之解析器

解析就是将模版解析成AST。 <div id"app"><p>{{num}}</p> </div>比如下面这个代码&#xff0c;然后转成AST之后是这个样子。 它是用javascript对象来描述一个接待您&#xff0c;一个对象表示一个节点。对象中的属性用来保存节点所需的各种数…

Docker基本概念+命令

Docker基本概念命令 一、Docker是什么&#xff1f;二、为什么Docker技术受欢迎三、Docker核心概念四、Docker安装五、Docker镜像操作1.搜索镜像2.获取镜像3.镜像加速下载4.查看镜像信息5.查看下载的镜像文件信息6.查看下载到本地的所有镜像7.获取镜像的详细信息8.修改镜像标签9…

【复盘与分享】第十一届泰迪杯B题:产品订单的数据分析与需求预测

文章目录 题目第一问第二问2.1 数据预处理2.2 数据集分析2.2.1 训练集2.2.2 预测集 2.3 特征工程2.4 模型建立2.4.1 模型框架和评价指标2.4.2 模型建立2.4.3 误差分析和特征筛选2.4.4 新品模型 2.5 模型融合2.6 预测方法2.7 总结 结尾 距离比赛结束已经过去两个多月了。 整个过…

手机变局2023:一场瞄准产品和技术的“思维革命”

以折叠屏冲高端&#xff0c;已成为中国手机厂商们的共识。 在这个苹果未涉足的领域&#xff0c;国产手机厂商们加快脚步迭代推新&#xff0c;积极抢占机遇。但平心而论&#xff0c;虽然国产折叠屏机型众多&#xff0c;但市场上始终缺乏一款突破性的产品作为标杆&#xff0c;为…

前端监控一vue指令实现埋点

前端监控一vue指令实现埋点 https://v2.vuejs.org/v2/guide/custom-directive.html 自定义指令 需要在main.js中执行 import Vue from vue // 自定义埋点指令 Vue.directive(track, {//钩子函数&#xff0c;只调用一次&#xff0c;指令第一次绑定到元素时调用。在这里可以…

【100天精通python】Day11:面向对象编程_类的定义和使用

目录 1. 面向对象的程序设计概述 2 类的定义和使用 2.1 定义类&#xff1a; 2.2 创建对象实例&#xff1a; 2.3 创建_init_() 方法 2.4 创建类的成员并访问 2.5 访问限制 2.5.1 公开访问&#xff08;Public Access&#xff09;&#xff1a; 2.5.2 私有访问&#xff08;…

深入学习 Redis - 深挖经典数据类型之 set

目录 前言 一、Set 类型 1.1、操作命令 sadd / smembers&#xff08;添加&#xff09; sismember&#xff08;判断存在&#xff09; scard&#xff08;获取元素个数&#xff09; spop&#xff08;删除元素&#xff09; smove&#xff08;移动&#xff09; srem&#x…

剑指27 二叉树的镜像 28.对称的二叉树 26.树的子结构

方法1&#xff1a;队列迭代 方法2&#xff1a;递归 队列迭代&#xff1a; class Solution { public:TreeNode* mirrorTree(TreeNode* root) {queue<TreeNode*> q;if(rootNULL) return root;q.push(root);while(!q.empty()){TreeNode *curq.front();if(!cur) continue;//…

Golang并发控制

开发 go 程序的时候&#xff0c;时常需要使用 goroutine 并发处理任务&#xff0c;有时候这些 goroutine 是相互独立的&#xff0c;需要保证并发的数据安全性&#xff0c;也有的时候&#xff0c;goroutine 之间要进行同步与通信&#xff0c;主 goroutine 需要控制它所属的子gor…

MySQL_2.3【高级查询】超详细讲解

1. distinct关键字 distinct # 在MySQL中&#xff0c;DISTINCT关键字用于消除重复记录&#xff0c;并返回唯一的记录集。 # DISTINCT关键字可以用在SELECT语句的开头&#xff0c;并在查询结果中显示唯一的行。 # 语法如下&#xff1a; select distinct 列1, ... , 列n from t…

vcomp100.dll丢失怎样修复?总结三个简单的修复方法

最近我遇到了一个问题&#xff0c;我的电脑上出现了vcomp100.dll文件丢失的错误。这个错误导致我无法运行一些使用了Microsoft Visual C编写的程序。当我第一次遇到这个问题时&#xff0c;我感到非常困惑和沮丧&#xff0c;因为我不知道如何解决这个问题。 然后&#xff0c;我开…

服务器操作手册——Slurm常用命令

文章目录 引言正文Slurm集群、节点、分区介绍Salloc申请节点并进入查看已经申请的节点终止作业查看集群情况退出节点具体运行问题无法联网问题安装的包找不到 引言 实验室的服务器的操作指令&#xff0c;之前同学写的不够详细&#xff0c;或者说有点乱&#xff0c;这里做一个简…

C++(类与对象)详解 - 1

C&#xff08;类与对象&#xff09;详解 - 1 1.面向过程和面向对象初步认识2.类的引入3.类的定义4.类的访问限定符及封装4.1 访问限定符4.2 封装 5.类的作用域6.类的实例化7.类的对象大小的计算7.1 如何计算类对象的大小7.2 类对象的存储方式7.3 结构体内存对齐规则 8.类成员函…

项目4渗透全过程

网络拓扑图 任务从拓扑图中可以其中一台web服务器可以进行与kali的直接通信。该web服务器是双重网段。也就是拿到该服务器就可以在进行下一步内网操作了。 2008配置&#xff1a; 护卫神主机系统密码&#xff1a;!#Qwe123. sqlserver2008密码&#xff1a;!#a123… 一、信息收…

遥感目标检测(3)-DAL(Dynamic Anchor Learning for Object Detection)

目录 一、概述 二、背景 三、建议 1、旋转RetinaNet 2、动态锚框分布 3、匹配敏感损失 四、实验 一、概述 由于选择正样本锚框进行回归&#xff0c;不一定能够定位真实的GT&#xff0c;而部分负样本回归甚至可以回归到真实的GT&#xff0c;说明相当多的负样本锚框有着准…

FreeRTOS 初识

从这节开始学习FreeRTOS操作系统。 FreeRTOS 介绍 Q: 什么是 FreeRTOS &#xff1f; A: Free即免费的&#xff0c;RTOS的全称是Real time operating system&#xff0c;中文就是实时操作系统。 注意&#xff1a;RTOS不是指某一个确定的系统&#xff0c;而是指一类操作系统。比…

【Java 并发编程】读写锁 ReentrantReadWriteLock StampLock 详解

读写锁 ReentrantReadWriteLock & StampLock 详解 1. 读写锁1.1 并发场景1.2 什么是读写锁1.3 思考如何自己实现一把锁&#xff1f; 2. ReentrantReadWriteLock2.1 ReentrantReadWriteLock 概述及其基本结构2.2 ReentrantReadWriteLock 的特点2.2.1 读写锁的互斥关系2.2.2 …

springboot实现qq邮箱发送邮件或者验证码

首先我先去qq邮箱或者网易邮箱开通POP3/IMAP/SMTP/Exchange/CardDAV 服务 它在左上角的设置——账户——往下滑就可以找到——然后点击开通 开通后就会得到一串授权码。如下图 接下来直接编写代码 首先我没导入依赖 <!-- 这个是邮箱验证--> <dependency> <group…