priority_queue模拟实现【C++】

news2024/11/24 20:08:46

文章目录

  • 全部的实现代码放在了文章末尾
  • 什么是适配器模式?
  • 准备工作
    • 包含头文件
    • 定义命名空间
    • 类的成员变量
    • 什么是仿函数?
    • 比较仿函数在priority_queue中的作用
      • 通过传入不同的仿函数可以做到大堆和小堆之间的切换
      • 通过传入不同的仿函数可以做到改变priority_queue里面的比较逻辑
    • 构造函数
      • 默认构造
      • 迭代器区间构造
    • size
    • empty
    • top
    • push
    • pop
  • 全部代码

全部的实现代码放在了文章末尾

【不了解堆(priority_queue)的可以看我这篇文章:模拟实现堆的接口函数】

priority_queue的模拟实现和stack,queue一样,采用了C++适配器模式

priority_queue的适配器一般是vector,也可以是deque

因为priority_queue是有特殊限制的线性表【只能取堆顶数据,并且需要高效的尾插尾删,还要支持高效的下标访问因为priority_queue的push和pop一般是调用适配器的尾插,尾删。 实现向下(上)调整算法的时候需要高效的下标访问方法)】
所以只要是线性结构并且可以高效的实现尾插和尾删,支持高效的下标访问的线性表,就可以作为priority_queue的适配器


什么是适配器模式?

适配器模式是一种设计模式,它允许将不兼容接口的类一起工作

适配器模式通常用于以下情况:

  1. 希望使用一个类,但其接口与其他代码不兼容。
  2. 希望创建一个可重用的类,它能够将接口转换为其他接口。
  3. 希望使用第三方库或遗留代码,但其接口与其他代码不兼容。

适配器模式通常包括以下三个主要部分:

  1. 目标接口(Target):这是期望使用的接口,客户端代码只能与目标接口交互。
  2. 源接口(Adaptee):这是需要适配的类,其接口与目标接口不兼容。
  3. 适配器(Adapter):这是一个类,它实现了目标接口,并将调用转换为对源接口的调用。适配器将源接口的调用转换为目标接口的调用,使得客户端代码可以与目标接口交互。

可以类比我们生活中的家庭电源接口笔记本电脑充电口电源适配器,它们之间也是一种适配器关系

笔记本电脑充电口是上面提到的目标接口
家庭电源接口是上面提到的源接口
电源适配器是上面提到的适配器

笔记本电脑的充电口是不能和家庭电源接口直接连接进行充电的,因为笔记本电脑用的是直流电,而家庭电源输出的是交流电,所以要把交流电转换为直流电才能给笔记本电脑供电,而电源适配器就能做到这一点

对应了上面提到的适配器模式解决的问题:
可以将不兼容接口的类一起工作


准备工作

创建两个文件,一个头文件mypriority_queue.hpp,一个源文件test.cpp

【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件mypriority_queue.hpp中】

  1. mypriority_queue.hpp:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义

  2. test.cpp:存放main函数,以及测试代码


包含头文件

  1. iostream:用于输入输出
  2. vector:提供vector类型的适配对象
  3. deque: 提供deque类型的适配对象
  4. assert.h:提供assert报错函数

定义命名空间

在文件mypriority_queue.hpp中定义上一个命名空间mypriority_queue
把priority_queue类和它的成员函数放进命名空间封装起来,防止与包含的头文件中的函数/变量重名的冲突问题


类的成员变量

有两个一个是适配器对象,一个是提供比较的仿函数,默认con是vector类型,comp是std库里面的less
在这里插入图片描述


什么是仿函数?

C++中的仿函数是指那些能够像函数一样调用的对象,它们的类至少需要有成员函数operator()()
仿函数可以是任何类型的对象,只要它的类重载了运算符(),它就能够被调用,就可以作为函数使用。
在C++中,标准模板库(STL)中的一些容器算法使用仿函数来封装各种操作,使得这些操作可以应用于容器中的元素。

仿函数可以是用户自定义的,也可以是C++标准库中定义的。例如,STL中的std::lessstd::greater就是两个标准的比较仿函数,它们用于排序算法中。用户也可以定义自己的仿函数

总结
C++的仿函数是重载了operator()的类实例化的对象,它可以被像函数那样调用

举个例子:
下图是一个可以简单进行大于比较的仿函数和它的类
在这里插入图片描述


比较仿函数在priority_queue中的作用

通过传入不同的仿函数可以做到大堆和小堆之间的切换

【stl库里面是传入less类型的仿函数为大堆,传入greater为小堆。 即大于是小堆,小于是大堆
为什么呢?
因为priority_queue需要用到比较的地方是向上(下)调整算法
如下图:
【向上(调整)这篇文章不做详细讲解,不了解的可以看我这篇文章:模拟实现堆的接口函数】
在这里插入图片描述

在这里插入图片描述
根据传给priorit_queue的第三个模板参数的不同,就可以得到不同类型的comp
这样调用comp实现的功能就不一样
std::less类型的comp的功能是判断左参数是否小于右参数
std::greater类型的comp的功能是判断左参数是否大于右参数
通过这一点就可以调整大小堆了
因为
如果comp是小于,那么comp(con[parent],con[child])就是父亲节点<孩子节点就交换,那么就会把大的节点一直往堆顶送,就可以实现大堆
comp是小于的时候同理


通过传入不同的仿函数可以做到改变priority_queue里面的比较逻辑

我们使用priority_queue的时候,一般不需要我们传入我们自己写的仿函数,使用库里面的std::lessstd::greater就可以满足我们的大部分需求

但是总规有一些情况,库里面的比较仿函数的比较逻辑不符合我们的要求
此时就需要我们自己写一个比较仿函数,传给priority_queue


如果我们定义了一个坐标类pos
在这里插入图片描述

假设我们比较两个pos对象的大小的时候分两种情况

  1. 优先比x,谁的x大谁就大,x相等时y大就大
  2. 优先比y,谁的y大谁就大,y相等时x大就大

如果我们用这两种比较方式建出来的堆(priority_queue)肯定是不同的

这个时候库里面的仿函数就满足不了我们的要求了

我们要自己写仿函数:
在这里插入图片描述

此时传入不同的仿函数,得到的堆就不同

在这里插入图片描述

在这里插入图片描述


构造函数

默认构造

由于默认构造可以直接传入适配器对象,因为传入的适配器对象里面可能已经有数据了,所以要把这些数据给建成堆,就算传入的适配器对象是空的也能处理
在这里插入图片描述


在这里插入图片描述


迭代器区间构造

在这里插入图片描述


在这里插入图片描述


size

因为把priority_queue的数据都存储在了适配器对象里面

所以适配器对象的size,就是priority_queue的size

加const是为了让const修饰的对象也能调用

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

empty

因为把priority_queue的数据都存储在了适配器对象里面

所以判断适配器对象是否为空即可
加const是为了让const修饰的对象也能调用

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

top

堆顶的数据只支持读取,  不支持  修改
因为改了就有可能不是 堆 了
所以返回值是const T&

const T& top() const
{
    因为把priority_queue的数据都存储在了适配器对象里面
	而且堆顶的数据存储在适配器对象的第一个数据位置
	所以直接调用front即可
	return con.front();
}

push

void push(const T& val)
{
	因为要把priority_queue的数据都存储在适配器对象里面
	所以新数据,直接尾插到适配器对象里面存储
	con.push_back(val);

	再堆新插入的数据调用向上调整算法
	保持插入之后也还是  堆  结构
	AdiuUp(size() - 1);
}

pop

void pop()
{
	堆不为空才能删除
	assert(!empty());

	把堆顶与适配器最后一个数据交换
	方便尾删
	std::swap(con[0], con[size() - 1]);

	尾删,把原来的堆顶数据删除
	con.pop_back();

	对换到堆顶的数据进行向下调整算法
	保持删除之后也还是  堆  结构
	AdiuDown(0);
}

全部代码

#include<iostream>
#include<vector>
#include<deque>
#include<assert.h>
using namespace std;

namespace mypriority_queue
{
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	private:
		//拥有比较大小能力的仿函数
		Compare comp;

		//适配器对象
		Container con;
	    
		//向上调整算法
		void AdiuUp(int child)
		{
			//找到  逻辑结构  中的父亲节点
			int parent = (child - 1) / 2;

			while (child > 0)
			{
				//使用  比较仿函数comp  进行比较大小
				//默认的comp是 std::less 类型的
				//即  左参数<右参数  就返回true,否则返回false
				if (comp(con[parent],con[child]))
				{
					std::swap(con[parent], con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		//向下调整算法
		void AdiuDown(int parent)
		{
			int n = this->size();

			//找到  逻辑结构  中的左孩子节点
			int child = parent*2+1;
			while (child <n)
			{
				if (child + 1 < n && comp(con[child], con[child + 1]))
				{
					child++;
				}
				//使用  比较仿函数comp  进行比较大小
				//默认的comp是 std::less 类型的
				if (comp(con[parent], con[child]))
				{
					std::swap(con[parent], con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	public:

		priority_queue(const Container & ctnr = Container(),const Compare & comp = Compare())
		{
			this->comp = comp;

			this->con = ctnr;

			int n = con.size();
			//建堆,从最后一个叶子节点的父亲节点开始
			//n-1为逻辑结构上的  最后一个叶子节点的下标 
			//它的父亲节点的下标等于  它的下标-1,再除以2
			for (int i = (n - 1 - 1) / 2; i >= 0; i--)
			{
				//向下调整算法
				AdiuDown(i);
			}
		}

		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			//遍历迭代器区间
			while (first != last)
			{
				//复用push进行插入
				push(*first);
				++first;
			}
		}

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

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

		//堆顶的数据只支持读取,  不支持  修改
		//因为改了就有可能不是 堆 了
		//所以返回值是const T&
		const T& top() const
		{
			//堆顶的数据存储在适配器对象的第一个数据位置
			return con.front();
		}


		void push(const T& val)
		{
			//因为要把priority_queue的数据都存储在适配器对象里面
			//所以新数据,直接尾插到适配器对象里面存储
			con.push_back(val);

			//再堆新插入的数据调用向上调整算法
			//保持插入之后也还是  堆  结构
			AdiuUp(size() - 1);
		}


		void pop()
		{
			//堆不为空才能删除
			assert(!empty());

			//把堆顶与适配器最后一个数据交换
			//方便尾删
			std::swap(con[0], con[size() - 1]);

			//尾删,把原来的堆顶数据删除
			con.pop_back();

			//对换到堆顶的数据进行向下调整算法
			//保持删除之后也还是  堆  结构
			AdiuDown(0);
		}
	};
}

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

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

相关文章

[Leetcode][Medium]-面试题 17.14.最小k个数-TOP K问题-快排/大根堆

一、题目描述 原题地址 二、整体思路 (1)、快排 数组中最小的k个数就是说把数组升序排列&#xff0c;求[0,k-1]区间上的数。 快排可以得到一个元素在升序排序的数组中的正确位置。在这个位置的左边区间[l,l2-1]上的元素都比它小&#xff0c;在这个位置的右边区间[r2,r]上的元素…

zabbix 监控软件

zabbix 监控软件 自带图形化界面&#xff0c;通过网页就可以监控所有的服务器的状态。 事件告警&#xff0c;邮箱通知&#xff08;噩梦&#xff09;。 zabbix是什么&#xff1f; web界面提供的分布式监控以及网络监控功能的开源的企业级的软件解决方案。 服务端 监控端 客…

App安装来源追踪的四大方案解析

App的开发者和运营商&#xff0c;都会研究分析渠道的效果&#xff0c;而对渠道来源的追根溯源是一切分析的基础。假如没有明确的安装来源数据&#xff0c;至少会造成以下几种后果&#xff1a; 没有安装来源数据&#xff0c;我们无法判断各个投放渠道流量的价值&#xff0c;也就…

全网首发!鸿蒙OS登上PC,冒风险流出内测,系统界面截图,过会儿就删

开玩笑的啦&#xff0c;其实这是Deepin操作系统的截图&#xff0c;很漂亮吧&#xff0c;这是deepin v23 rc2 的主题之一 鸿蒙还没有发&#xff0c;不知道24年末还能不能上&#xff0c;emmm

Python实战:基础语法

一、求解列表中的最大元素 import random#定义函数 def get_max(lst):x lst[0] #x存储的是元素的最大值#遍历操作for i in range(1,len(lst)):if lst[i] > x:x lst[i] #对最大值进行重新赋值return x#调用函数 lst [random.randint(1,100) for item in range(10)] print…

基于SiliconCloud快速体验GraphRag.Net

SiliconCloud介绍 SiliconCloud 基于优秀的开源基础模型&#xff0c;提供高性价比的 GenAI 服务。 不同于多数大模型云服务平台只提供自家大模型 API&#xff0c;SiliconCloud上架了包括 Qwen、DeepSeek、GLM、Yi、Mistral、LLaMA 3、SDXL、InstantID 在内的多种开源大语言模…

sgetrf M N is 103040 时报错,这是个bug么 lapack and Openblas the same,修复备忘

1,现象 MN103040时&#xff0c;调用 sgetrf_ 时&#xff0c;无论是 LAPACK 还是 OpenBLAS&#xff0c;都出错&#xff1a; openblas&#xff1a; lapack&#xff1a; 2, 复现代码 出现问题的应该是由于M和N相对数字太大&#xff0c;乘积超出32bit整数的表达范围&#xff0c;…

【踩坑】TypeScript 中使用 sass 动态设置样式

问题 在从 Vue2 项目转向 Vue3 项目时&#xff0c;不得不将已经封装好的 echarts 图表也升级成 Vue3 适配的版本&#xff0c;遇到了一个有些诡异的问题&#xff0c;在此记录一下。 背景&#xff1a; 在 Vue2 的项目中&#xff0c;为了动态设置 echarts 的相关配置&#xff0c;于…

trie算法

1、定义 高效的存储和查找字符串集合的数据结构 它的优点是&#xff1a;利用字符串的公共前缀来减少查询时间&#xff0c;最大限度地减少无谓的字符串比较&#xff0c;查询效率比哈希树高 2、构建 我们可以使用数组来模拟实现Trie树。 我们设计一个二维数组 son[N] [26] 来…

WhatsApp收不到验证短信的原因及解决方案

在使用WhatsApp进行账号注册或验证过程中&#xff0c;有时会遇到无法收到验证短信的情况。这种情况可能会给用户带来诸多不便&#xff0c;但通常可以通过一些简单的方法来解决。本文将详细分析收不到验证短信的可能原因&#xff0c;并提供相应的解决方案&#xff0c;帮助用户顺…

LeetCode_sql_day15(262.行程与用户)

描述&#xff1a;262. 行程和用户 - 力扣&#xff08;LeetCode&#xff09; 取消率 的计算方式如下&#xff1a;(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数)。 编写解决方案找出 "2013-10-01" 至 "2013-10-03" 期间非禁止…

EMF矢量图工具Graphpad Prism(棱镜科研绘图工具)

Graphpad Prism 是一款功能强大、专业实用的棱镜科研绘图软件&#xff0c;专为科研工作者而设计研发&#xff0c;可帮助用户进行专业便捷的科研图像绘制&#xff0c;通过该款软件用户可以进行新型子列图进行创建&#xff0c;可以进行平滑的线性图进行绘制&#xff0c;可以说是一…

【汉明距离总和】python刷题记录

R4-数与位篇 class Solution:def totalHammingDistance(self, nums: List[int]) -> int:#创建计数器trieCounter()max_bitlen(bin(max(nums)))-2ret0for i,num in enumerate(nums):for j in range(max_bit):#一位位地取出来bit(num>>j)&1if bit:reti-trie[j]trie[…

同态加密和SEAL库的介绍(一)简介

写在前面&#xff1a; 最近在做同态相关的内容&#xff0c;这里记录下相关的知识点和所踩过的坑&#xff0c;希望对大家有帮助。预计分几篇来详细介绍&#xff0c;从概念简介到不同模式介绍&#xff0c;具体包括了每种模式的编解码和加解密以及他们性能的比对。 虽然同…

MySQL 8.0新特性

文章目录 一. 账户与安全1. 查看用户信息2. 用户权限管理范围3. 用户创建和授权1&#xff09; 创建并授权用户2&#xff09;登录zhp&#xff0c;密码zhp.1221。验证数据库权限3&#xff09;查看用户权限4&#xff09;撤销用户权限5&#xff09;用户重命名&修改密码6&#x…

端到端 AWS 定量分析:使用 AWS 和 AWSCLI 自动运行脚本

使用 AWSCLI 启动、运行和关闭 AWS 服务器 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 欢迎来到雲闪世界。我们开发了两个 Python 脚本&#xff1b;一个用于为我们获取数据&#xff0c;另一个用于使用 sklearn 的决策树分类器处理数据。然后&#xf…

NAT、服务代理、内网穿透

文章目录 NAT技术NAT IP转换过程NATPNAT的优点NAT的缺点 代理服务器正向代理反向代理 内网穿透和内网打洞内网穿透内网穿透 NAT技术 NAT技术即网络地址转换技术。用于将私有IP地址转换为公共IP地址&#xff0c;以便在互联网或其他外部网络中通信。为了解决IPv4协议下IP地址不足…

【Nacos无压力源码领读】(三) Nacos 配置中心与热更新原理详解超详细解读

本文将从 Nacos 配置中心的基本使用入手, 详细介绍 Nacos 客户端发布配置, 拉取配置, 订阅配置的过程以及服务器对应的处理过程; 配置订阅以及热更新原理相关的部分, 我看了主流的博客网站, 绝对没有比这更详细的讲解; 如果在阅读过程中对文中提到的 SpringBoot 启动过程以及…

交叉编译nginx1.20.0

一、说明 简略写一下过程&#xff0c;仅用于参考&#xff0c;建议与其他交叉编译教程一起看&#xff0c;检查是否有遗漏的问题。 二、源码修改 1、auto/cc/name vi auto/cc/name 注释 21 行 exit 1。 2、auto/types/sizeof vi auto/types/sizeof 将 15 行处的” ngx_size”…

数字图像处理(理论篇)专栏介绍

专栏导读 数字图像处理是计算机视觉领域的基石&#xff0c;它涉及到图像的获取、表示、处理和分析等多个方面。本专栏将通过一系列精心挑选的实战案例&#xff0c;引导读者从基础概念到高级技术&#xff0c;逐步深入学习数字图像处理的各个方面。 专栏目录 数字图像处理 第一…