【C++】STL | priority_queue 堆(优先级队列)详解(使用+底层实现)、仿函数的引入、容器适配器的使用

news2024/12/24 2:47:41

目录

前言

总代码

堆的简介

仿函数

堆的基础框架建立size、empty、top、

向上调整法 and push

向上调整

push

向下调整法 and pop

向下调整法

pop

迭代器区间初始化(构造)

逻辑讲解

为何选择向下建堆?

建堆代码实现

结语


前言

本篇博客主要讲解的重点在于仿函数和容器适配器

如果有希望了解堆是什么东西的,可以看一下下方链接的博客,里面对堆的相关知识讲解非常详细,当然下文也会进行相关的讲解

堆-时间复杂度讲解-topk问题

而堆的函数使用并不困难,如下:

也就不到10个函数,我们在下文都有所讲解

总代码

如果有友友只是复习需要,只想看完整代码的话,可以直接点下面的gitee链接

当然对于看完了整篇文章的友友也可以照着这里的代码敲一篇进一步理解喔...(* ̄0 ̄)ノ

堆 - priority_queue - blog - 2024-08-08

堆的简介

堆是一个树状结构,如果是小堆的话,其遵循的规则就是父节点 < 子节点,如下:

如果是大堆的话,那么就是父节点 > 子节点

而我们的堆其实是存储在一个数组上的,我们找一个节点的父节点或子节点都遵循着一定的数学规律

父节点 = (子节点 - 1)/ 2

子节点 = 父节点 * 2 + 1(左节点)   /   +2(右节点)

如上图,真实的情况应该是一个数组,一段连续的空间存储着一串数字

而我们的堆则是被抽象出来的,并不是说真的就是像一棵树一样存在内存里,只是抽象成一棵树的形状更利于我们看

仿函数

仿函数是什么呢?

func();

如果我们只看这个代码的话,你会觉得这是什么?一个函数调用对吧

但如果我告诉你,这是一个类里面重载了一个operator(),所以我们在外面使用()其实就是调用了那个类里面的operator()

struct func
{
    void operator()()
    {
        cout<<"hello world"<<endl;
    }

};
hello world

那么我们为什么要在堆这个章节讲解这个内容呢?

试想一下,我们的堆分为大堆和小堆,我们如果想要实现大堆,是不是只能进入内部自己去改><啊,但是这样子的话太挫了,我们如果能在外面自己控制就好了

所以,就有了仿函数的出现

我们只需要自己写一个仿函数作为模板的参数传给堆,我们就可以实现在外面控制大堆小堆了

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

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

堆的模板参数,第三个是仿函数,第二个是容器适配器
template<class T, class container = vector<T>, class compare = myless<T>>

我们能看到,在上面的代码中我们写了一个myless和一个mygreater

返回的就是一个bool而已

但是我们可以实现在类里面像函数一样调用他,如果我们直接写一个函数的话封装就被破坏了

而我们在堆的模板参数上也把这个加上去了,并且我们还给了一个缺省值,默认其初始情况为大堆

其实这个仿函数厉害的一点就在于他的灵活,如果我们这个堆里面存储的是一个自定义类型的话,我们标准库里的仿函数可能在某些情况下效率并不高或者说没用,这时我们甚至可以自己写一个仿函数传过去

struct date_less
{
    bool operator()(Date* d1, Date* d2)
    {
        return d1->year < d2->year;
    }
};

struct date_less
{
    bool operator()(Date* d1, Date* d2)
    {
        return d1->month< d2->month;
    }
};

struct date_less
{
    bool operator()(Date* d1, Date* d2)
    {
        return d1->day< d2->day;
    }
};

我们可以自己根据情况自己实现仿函数传给堆,这可是相当厉害的

堆的基础框架建立size、empty、top、

我们的堆使用的和stack(栈)、queue(队列)一样,都是适配器模式

如果有不理解什么是适配器模式的,可以简单看一下下面这一篇文章,里面有相关讲解

【STL】| C++ 栈和队列(详解、deque(双端队列)介绍、容器适配器的初步引入)

所以我们可以在模板参数这个部分可以将容器适配器传过来并给一个缺省值——vector<T>

然后我们后面还需要再加一个仿函数作为第三个参数,这个可以帮助我们灵活地控制大小堆

而我们堆的成员函数就只有一个仿函数

然后我们里面的大部分函数都可以使用仿函数里面的函数实现

代码如下:

//仿函数
template<class T>
struct myless
{
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

//仿函数
template<class T>
struct mygreater
{
	bool operator()(const T& x, const T& y)
	{
		return x > y;
	}
};



template<class T, class container = vector<T>, class compare = myless<T>>
class priority_queue
{
public:
    //强制生成构造函数
	priority_queue() = default;

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

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

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

private:
	container _con;
};

向上调整法 and push

向上调整

如果我们要插入一个新数据的话,可以选择在最尾部插入一个数据,然后将其慢慢向上调整即可

而我们函数的参数是孩子节点的位置,我们需要先在函数内将其parent找出来

父节点 = (子节点 - 1)/ 2

子节点 = 父节点 * 2 + 1(左节点)   /   +2(右节点)

然后写一个while循环表示一直向上寻找、比较

如果父<子(大堆)或者父>子(小堆),我们就将父子两个节点互换,然后孩子节点跑到parent的位置,parent节点继续向上寻找新的parent

如果child节点<0了,就证明到头了

或者如果不满足这关系的话(父<子(大堆)或者父>子(小堆)),那就直接break

值得注意的是吗,我们的比较逻辑可以直接问放进仿函数里面,由仿函数来控制

代码如下:

template<class T, class container = vector<T>, class compare = myless<T>>



    void AdjustUp(int child)
    {
	    compare comf;
	    int parent = (child - 1) / 2;
	    while (child > 0)
	    {
		    if (comf(_con[child], _con[parent]))
		    {
			    swap(_con[child], _con[parent]);
			    child = parent;
			    parent = (parent - 1) / 2;
		    }
		    else
			    break;
	    }
    }

push

我们的push逻辑就简单多了,直接利用仿函数自带的push_back尾插数据,然后让这个数据执行一次向上调整的逻辑即可

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

向下调整法 and pop

向下调整法

向下调整法最关键的一个点就是——比较父节点与子节点,满足条件就交换,然后向下,不满足条件就break

只不过问题的关键就在于(假设建大堆),我们怎么知道是左节点大还是右节点大?

我们无从得知,所以我们就用一招假设法,我假设左节点比较大

然后下面再给一个 if 判断一下,如果左确实大,那就不管,如果右比较大,那我就将child变成右即可

然后还是按照条件找子,然后判断大小,判断是否要交换

如果要交换,那就交换完之后再向下找,直到不满足交换条件或者超出数组大小了,再结束

如果不满足条件,就直接break

代码如下:

template<class T, class container = vector<T>, class compare = myless<T>>



    void AdjustDown(int parent)
    {
	    compare comf;
	    int child = parent * 2 + 1;
	    while (child < _con.size())
	    {
		    if (child + 1 < _con.size() && comf(_con[child + 1], _con[child]))child++;

		    if (comf(_con[child], _con[parent]))
		    {
			    swap(_con[child], _con[parent]);
			    parent = child;
			    child = child * 2 + 1;
		    }
		    else break;
	    }
    }

pop

图示如下:

我们可以看到,pop的主要逻辑就是先将首位交换然后再去掉尾,这样就实现了出堆

然后再将最上面的数据依次向下调整即可

代码如下:

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

迭代器区间初始化(构造)

逻辑讲解

为了适配所有的迭代器(因为迭代器的本质就是模仿指针,我们要的只是迭代器指向的数据而不是迭代器本身),所以我们需要写一个模板

template<class InputIterator>

我们在函数里面只需要写一个迭代器遍历的逻辑,然后依次将数据尾插进堆里面

注意,我们这里并不复用我们上面写到的push,因为push的逻辑是向上调整建堆,实际上向下调整建堆的效率会更高(这点我们在下文会讲)

所以我们先使用容器适配器自带的push_back插入迭代器指向的数据

然后我们再自己实现一个向下建堆的逻辑

为何选择向下建堆?

注意看,如果我们选择向上建堆的话,我们就需要从58开始,一个一个向上遍历,需要遍历7次

而我们如果向下调整的话,我们则是从56开始遍历,只需要遍历3次!!!

或许你会觉得,也就是几次而已,但如果我们的堆有100层呢?

那我们向下调整建堆会比向上调整建堆效率(满节点的情况下)高上一倍

如果向上要用30s,那么我向下只需要15s(理想情况下)

所以我们肯定是自己写以一个向下调整建堆的逻辑比较好

建堆代码实现

我们需要的是从最后一个节点的父节点位置开始遍历,所以我们可以写一个for循环,i 就可以从最后一个位置开始找父节点

最后一个位置是 _con.size()-1

父节点 = (子节点 - 1)/ 2

子节点 = 父节点 * 2 + 1(左节点)   /   +2(右节点)

综上,i = (_con.size()-1    -1)/ 2

代码如下:

template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		_con.push_back(*first);
		first++;
	}
	//建堆
	for (int i = ((int)(_con.size()) - 2) / 2; i >= 0; i--)
	{
		AdjustDown(i);
	}
}

各位会看到我上面写的是 i = (     (int)(_con.size())  -1    -1)/ 2

这是因为size()返回的是一个size_t类型的数据,但是我们要让他-1就是整数了(int),所以最好强转一下(不会报警告)

结语

这篇博客到这里,对堆(优先级队列)的讲解就结束啦(~ ̄▽ ̄)~

如果觉得对你有帮助的话,请务必多多支持博主喔<(_ _)>~( ̄▽ ̄)~*

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

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

相关文章

区块链的搭建和运维4

区块链的搭建和运维4 (1) 搭建基于MySQL分布式存储的区块链 1.构建单群组网络节点 使用开发部署工具构建单群组网络节点&#xff0c;命令如下&#xff1a; bash build_chain.sh -l 127.0.0.1:4 -p 30300,20200,85452. 启动 MySQL 并设置账户密码 输入如下命令&#xff0c;…

【mysql 第一篇章】系统和数据库的交互方法

一、宏观的查看系统怎么和数据库交互 在我们刚刚接触系统和数据库的时候不明白其中的原理&#xff0c;只知道系统和数据库是需要交互的。所以我们会理解成上图的形式。 二、MYSQL 驱动 随着我们的学习时间的加长以及对程序的了解&#xff0c;发现链接数据库是需要有别的工具辅…

免费【2024】springboot 高校毕业生信息管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

安卓Termux系统设备安装内网穿透工具实现远程使用SFTP传输文件

文章目录 前言1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 前言 本教程主要介绍如何在安卓 Termux 系统中使用 SFTP 文件传输&#xff0c;并结合cpolar内网穿透工具生成公网地址&#xff0c;轻松实现无公网IP环境远程传输&#xf…

用 echarts 开发地图、点击展示自定义信息框

1、下载所需地市的json 链接&#xff1a;DataV.GeoAtlas地理小工具系列 在右侧输入需要的名称&#xff0c;然后下载json文件到本地 2、在html 中准备容器&#xff0c;并设置宽高 <div id"mapContent"> <div ref"mapChart" style"width:10…

全网详解LVS的四种工作模式及案例

目录 LVS&#xff08;Linux virual server&#xff09; 一、集群和分布式的简介 二、LVS的运行原理 1、LVS简介 2、LVS 相关术语 3、LVS的集群类型 三、LVS-NAT工作模式 部署NAT工作模式案例&#xff1a; 1、实验环境 2、实验环境说明 3、配置 四、LVS-DR工作模式 …

Http:八股

1、Https加密方式 1.1Https通过 摘要算法保证数据的完整性&#xff0c; 1、服务器将公钥注册到CA&#xff0c; CA用自己的私钥给 服务器的公钥进行数字签名。 2、客户端拿到服务器证书后&#xff0c;用CA的公钥确认数字证书的真实性。 3、获取服务器的公钥&#xff0c;使用它对…

SpringBoot Actuator

对应用进行观测,监控,预警 健康状况[组件状态,存活状态] health 健康端点:返回存活,死亡. Health对象 运行指标[CPU,内存,垃圾回收,吞吐量,响应成功率] Metrics 指标监控端点:访问次数/率等等 链路追踪等等 引入web和actuator依赖 在…

如何在不丢失数据的情况下绕过IPhone密码?

不幸的是&#xff0c;不可能在不丢失数据的情况下绕过 iPhone 密码。通过密码的唯一方法是使用iTunes或iCloud恢复设备。这将清除您设备的内容&#xff0c;因此请务必在恢复之前备份所有重要数据。如果您忘记了密码&#xff0c;请按照以下步骤操作&#xff1a; 1. 将您的 iPhon…

AI绘画 Stable Diffusion后期处理—无需ControlNet也能轻松高清放大图像与老旧照片修复,SD新手必看教程

大家好&#xff0c;我是设计师阿威 分享了这么多期AI绘画Stable DIffusion的入门教程和一些常用的插件玩法后&#xff0c;不知道大家有没有发现&#xff0c;SD还有一个功能&#xff0c;似乎没怎么用到过&#xff0c;它就是—后期处理。 今天就给大家分享一下SD中的 “后期处理…

VLSI | 计算CMOS反相器的负载电容

ref. 数字集成电路 电路、系统与设计&#xff08;第二版&#xff09;&#xff0c;周润德 译 为了计算方便&#xff0c;本人编写了MATLAB代码进行计算&#xff0c;需要可至&#xff1a;MATLAB计算CMOS反相器等效负载电容 。资源中也给出了PTM的MOS模型参数。对于MOS的模型参数&…

TMGM:日本加息预期被推迟,日元相对稳定

根据最新的日本银行《意见总结》&#xff0c;"实现通胀目标的可能性进一步增加"&#xff0c;预计将进一步上升。 "假设通胀目标将在2025财年下半年实现&#xff0c;央行应在那时将政策利率提高到中性利率水平。由于中性利率水平至少在1%左右&#xff0c;为避免…

injected stylesheet 导致 页面重置按钮文字样式改变,按钮中的文字看不清晰

项目场景&#xff1a; 相关背景&#xff1a; injected stylesheet 导致 页面重置按钮文字样式改变&#xff0c;看不清晰 问题描述 遇到的问题&#xff1a; 检查页面中该按钮的代码如下所示&#xff1a; <div class"el-form-item__content"> <button d…

手把手从0开始,使用Ollama+OpenWebUI本地部署阿里通义千问Qwen2 AI大模型

&#x1f4a5;Ollama介绍 Ollama是一个开源框架&#xff0c;专为在本地机器上便捷部署和运行大型语言模型&#xff08;LLM&#xff09;而设计。它提供了一套简单的工具和命令&#xff0c;使任何人都可以轻松地启动和使用各种流行的LLM&#xff0c;例如GPT-3、Megatron-Turing N…

Python教程(十二):面向对象高级编程详解

目录 专栏列表前言变量命名规则说明&#xff1a;一、类的内部变量和特殊方法1.1 内部变量示例测试结果: 1.2 __slots__未使用__slots__使用__slots__ 二、装饰器2.1 函数装饰器示例 2.2 property示例 三、枚举类3.1 枚举类概述3.2 枚举类定义示例 四、元类4.1 什么是元类4.2 自…

去除猫咪浮毛哪款更胜一筹?希喂、有哈宠物空气净化器测试对比

随着养宠人群的增多&#xff0c;宠物空气净化器受到铲屎官们的喜爱&#xff0c;成为家庭清洁的好帮手。然而&#xff0c;市场上的选择繁多&#xff0c;不少品牌以次充好&#xff0c;让人们掉入消费陷阱。为此&#xff0c;挑选一台优质有保障的宠物空气净化器品牌&#xff0c;需…

智界S7 小鹏P7 G3 G3i P5 G9 P7i G6 X9维修手册和电路图线路图接线资料更新

汽修帮手资料库提供各大厂家车型维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表位置等&#xff0c;并长期保持高频率资料更新&#xff01; 覆盖车型2020-2024年智界S7 小鹏…

不是智商税!六个核桃健脑效果获科学支持,效果源自七大方面

这两年来&#xff0c;关于脑雾、脑损伤的话题在社交媒体上越发受到关注&#xff0c;健脑也越发成为国民刚需。 不过&#xff0c;要做到科学健脑并不容易。有消费者选择专业保健品补剂&#xff0c;也有消费者倾向于食补。中国饮食传统中就有核桃补脑的说法&#xff0c;但不少人…

Untiy Modbus 西门子 S7-1200 基础通信

Untiy Modbus 西门子 S7-1200 基础通信 ModbusModbus是什么Modbus 协议版本Modbus 通信和设备Modbus 如何实现Modbus 使用限制Modbus 通信协议学理上的弱点分析 UnityUnity ModbusTCPUnity ModbusTCP 单个线圈读取方法Unity ModbusTCP 单个线圈写入方法 IntUnity ModbusTCP 单个…

Android 让程序随系统自动启动并允许后台运行(白名单)

最近制作一个管理程序&#xff0c;需要在开机时候启动&#xff0c;并持续运行。这里简单记录下如何制作。 自启动原理 系统在启动的时候会广播一个ACTION_BOOT_COMPLETED&#xff0c;带有接收的程序可以收到&#xff0c;所以我们在接收到以后把程序运行起来。 清单文件设置 …