C++ --- priority_queue

  • 前言
  • 一、priority_queue的使用
  • 二、priority_queue的简单实现
    • 1.整体结构
    • 2.主要方法
      • push
      • pop
      • top
      • empty
      • size
  • 三、构造
    • 迭代器区间构造
    • 默认构造
  • 四、仿函数

前言

priority_queue是C++容器之一,意为优先级队列,虽说叫做队列,但是其底层结构是堆,唯一和队列有关是使用此容器时包含的标准头文件是queue,通过此容器的学习,会学习到一个新的知识,仿函数。

一、priority_queue的使用

priority_queue的接口和stack与queue的基本一样,主要是push,pop,top,empty,size这些接口,只是底层的结构不同。

需要注意的是底层默认创建大堆结构,需要变成创建小堆结构,这里需要使用新的知识,仿函数,仿函数的详细介绍留在简单实现板块;还有同样因为数据有特殊的顺序,所以底层不支持迭代器遍历

// 在这里仿函数是第三个模板参数,控制的是创建堆的结构
// greater --- 更大的
// less --- 更小的
// 不过标准库里的是less控制大堆,greater控制小堆,是反过来的,这一点需要注意一下
// 标准库提供的接口和前面的stack,queue相似,主要就是push,pop,top,size,empty这几个
// 使用起来也是差不多的,同样因为数据有特殊的顺序,所以底层不支持迭代器遍历
// 尽管堆的底层是数组,但是底层没有实[]的重载。// 创建一个堆的对象
//priority_queue<int> hp;
priority_queue<int, vector<int>, greater<int>> hp;hp.push(4);
hp.push(2);
hp.push(6);
hp.push(7);
hp.push(1);
hp.push(8);// 循环取堆顶元素打印
while (!hp.empty())
{cout << hp.top() << " ";hp.pop();
}
cout << endl;

打印结果:
此时仿函数是greater,创建的是小堆,循环取堆顶元素则是升序排列。
在这里插入图片描述

二、priority_queue的简单实现

1.整体结构

priority_queue的底层是一个堆结构,并且堆结构是由数组实现的完全二叉树,所以这里直接使用容器适配器,默认适配vector,来作为priority_queue的结构。

template<class T, class Container = vector<T>>
class priority_queue
{
public://………………// 各种方法
private:Container _con;
};

2.主要方法

priority_queue的主要接口就是push,pop,top,empty,size。

push

由于堆结构本身就有性质,要么是大堆,要么就是小堆,所以这里在堆尾插入完数据后需要调整数据的位置,以满足堆的结构。

// 在堆尾入数据
void push(const T& x)
{// 插入数据_con.push_back(x);// 向上调整算法Adjust_up(size() - 1);}// 向上调整算法
void Adjust_up(size_t child)
{// 已知孩子节点求双亲节点size_t parent = (child - 1) / 2;// 最坏的情况child调整至根节点才结束while (child > 0){// 大堆,谁大谁向上调整// 小堆,谁小谁向上调整if (_con[child] < _con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

在向上调整算法中,参数接收的是孩子节点的下标,根据二叉树的性质可知双亲节点的下标 = (孩子节点 - 1)/ 2,求出双亲节点的下标后比较它们两个的大小,孩子节点大(大堆)/ 小(小堆)于双亲节点,则进行交换,随后更新child和parent的位置,指向下一棵子树,若插入后满足所需要的堆结构,则直接跳出循环,最坏的情况下child调整至根节点才结束,所以这里的循环结束条件是child <= 0。

pop

由于堆底层是数组,并且堆的pop操作是在堆顶进行的,所以相当于进行头删操作,这样效率较低,首先先将堆顶数据和堆尾数据进行交换,这样一来需要删除的数据就在堆尾,而数组删除最后一个数据效率很高,直接将其删除掉即可,然后调整堆结构即可,这里使用向下调整算法。

// 在堆顶出数据
void pop()
{// 删除堆顶元素,先将堆顶和堆尾元素交换,再删除swap(_con[0], _con[_con.size() - 1]);_con.pop_back();// 向下调整算法Adjust_down(0);
}// 向下调整算法
void Adjust_down(size_t parent)
{// 已知双亲节点求左孩子节点,右孩子节点即左孩子 + 1size_t child = parent * 2 + 1;// 最坏的情况是根节点调整到叶子节点while (child < size()){// 首先右孩子得存在合法// 如果右孩子大于/小于左孩子,则child走到大/小的一方if (child+1 < size() && _con[child + 1] < _con[child]){++child;}// 大堆,谁大谁向上调整// 小堆,谁小谁向上调整if (_con[child] < _con[parent]){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

在向下调整算法中,参数接收的是根节点的下标,根据二叉树的性质可知左孩子节点 = 双亲节点 * 2 + 1,右孩子则是左孩子 + 1;在此算法中额外多出一步是比较左右孩子的大小关系,当堆结构为大堆时,child走到大的那一个孩子处,同理当堆结构为小堆时,child走到小的那一个孩子处(这一步要注意右孩子的下标需要存在合法),之后就和向上调整算法里的一样,孩子节点和双亲结点比较,谁大 / 小 就进行交换,随后更新child和parent的位置,走到下一棵子树的位置继续进行调整,若满足所需要的堆结构,则直接跳出循环,最坏的情况是根节点调整到叶子节点才结束,所以这里的循环结束条件是child >= 堆的szie。

top

此接口和下面的两个接口都直接使用适配即可。

// 取堆顶元素
const T& top() const
{return _con[0];
}

empty

// 判空
bool empty()
{return _con.empty();
}

size

// 取有效元素个数
size_t size() 
{return _con.size();
}

三、构造

迭代器区间构造

priority_queue支持迭代器区间构造,并且底层实现的是函数模板形式,所以我们也去实现函数模板形式。

// 迭代器区间构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)// 先将此迭代器区间入堆 --- _con是容器,支持直接迭代器区间构造:_con(first,last)
{// 然后就是向下调整建堆 --- 因为此时还不是堆结构for (size_t i = (size() - 1 - 1) / 2; i > 0; i--){Adjust_down(i);}
}

这里初始化列表里直接复用适配器的迭代器区间构造即可,然后虽入了数据,但是此时不是一个堆结构,同样需要进行调整,这里采用的是向下调整算法,至于为什么不使用向上调整算法,因为向上调整算法的时间复杂度高于向下调整算法,时间复杂度详情请回顾我的数据结构 — 堆 的这篇博客(link)

在向下调整建堆中,由于是向下调整,双亲节点和孩子节点进行比较向下调整,所以这里的for循环的循环变量 i 也就是双亲节点的下标,并且二叉树的最后一层节点是叶子节点,所以双亲节点的最后一层是倒数第二层,size() - 1 是最后一个位置的下标(不能保证此位置上有节点,此位置是右孩子),再减一就是左孩子的下标,已知孩子求双亲就是:(size() - 1 - 1) / 2,直至调整至根节点,所以循环结束条件就是i <= 0。

默认构造

由于我们自己写了一种构造,编译器就不再生成默认构造了,而默认构造能满足我们的需求,所以这里强制让编译器生成默认构造。

// 由于我们自己写了一种构造,编译器就不生成默认构造了
// 所以这里强制让编译器生成
priority_queue() = default;

四、仿函数

所谓仿函数,也叫做函数对象,它其实是一个类,其中重载了运算符operator(),使得这个类能够像函数一样被调用。
回到堆的代码中,在向上或者向下调整算法里面,我们控制大堆小堆结构是通过孩子和双亲大小关系来确定的,但是这个关系是固定死的,要么大于,要么小于,这里就可以使用仿函数,不用写死关系,通过调用仿函数创建的对象去调用运算符(),在此重载内部再去确定比较关系即可。

// 这个就是仿函数 / 函数对象
// 仿函数代替的就是C语言的函数指针
// 简单来说就是一个类,通过这个类类型创建的对象去模拟函数那样被调用
template<class T>
struct less
{bool operator()(const T& x, const T& y){return x < y;}
};template<class T>
struct greater
{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

回到向上调整算法中,在方法内部通过Compare创建了一个对象com,在孩子和双亲比较的逻辑里替换了之前的固定写死一方的大小关系,变成通过com对象去调用运算符(),这样就看起来像是调用了函数,这就是仿函数。

// 向上调整算法
void Adjust_up(size_t child)
{Compare com;size_t parent = (child - 1) / 2;while (child > 0){//if (_con[child] < _con[parent])if (com(_con[parent], _con[child])){swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

调用关系如下图所示:
这里的参数x接收的就是parent,参数y接收的就是child。
在这里插入图片描述
同理向下调整算法里parent和child的比较逻辑,child和child+1的比较逻辑也是如此替换。
需要注意的是对于类模板而言使用仿函数时这里传递的是类型,而对函数模板而言使用仿函数时传递的则是对象。

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

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

相关文章

MySQL梳理三:查询与优化

MySQL查询优化完整指南&#xff1a;从理论到实践 本文从MySQL查询的基础机制出发&#xff0c;深入探讨单表查询访问方法、联表查询策略、成本计算原理、基于规则的优化技术&#xff0c;最后通过实际案例展示慢SQL的诊断和优化过程。 目录 一、单表查询的访问方法二、联表查询机…

从零开始的python学习(九)P129+P130+P131+P132+P133

本文章记录观看B站python教程学习笔记和实践感悟&#xff0c;视频链接&#xff1a;【花了2万多买的Python教程全套&#xff0c;现在分享给大家&#xff0c;入门到精通(Python全栈开发教程)】 https://www.bilibili.com/video/BV1wD4y1o7AS/?p6&share_sourcecopy_web&v…

LCL滤波器及其电容电流前馈有源阻尼设计软件【LCLAD_designer】

本文主要介绍针对阮新波著《LCL型并网逆变器的控制技术》书籍 第二章&#xff08;LCL滤波器设计&#xff09;及第五章&#xff08;LCL型并网逆变器的电容电流反馈有源阻尼设计&#xff09;开发的一款交互式软件【LCL&AD_designer】&#xff0c;开发平台MATLAB_R2022b/app d…

【Conda】配置Conda镜像源

Conda 镜像源配置指南 适用系统&#xff1a;Windows 10&#xff08;含 Miniconda / Anaconda&#xff09; & Linux&#xff08;Ubuntu / CentOS / Debian 等&#xff09;1. 为什么要设置镜像源 在中国大陆直接访问 repo.anaconda.com 经常遇到速度慢、连接超时、SSL 错误等…

八股取士--docker

基础概念类 1. 什么是Docker&#xff1f;它解决了什么问题&#xff1f; 解析&#xff1a; Docker是一个开源的容器化平台&#xff0c;用于开发、交付和运行应用程序。 主要解决的问题&#xff1a; 环境一致性&#xff1a;解决"在我机器上能跑"的问题资源利用率&#…

C++:STL中的栈和队列的适配器deque

学习完string类、容器vector和容器list&#xff0c;再去学习其他容器的学习成本就非常低&#xff0c;容器的使用方法都大差不差&#xff0c;而栈和队列的底层使用了适配器&#xff0c;去模拟实现就没有那么麻烦&#xff0c;适配器也是一种容器&#xff0c;但是这种容器兼备栈和…

9类主流数据库 - 帮你更好地进行数据库选型!

作者&#xff1a;唐叔在学习 专栏&#xff1a;数据库学习 标签&#xff1a;数据库选型、MySQL、Redis、MongoDB、大数据存储、NoSQL、数据库优化、数据架构、AI数据库 大家好&#xff0c;我是你们的老朋友唐叔&#xff01;今天咱们来聊聊程序员吃饭的家伙之一 —— 数据库。在这…

推送本地项目到Gitee远程仓库

文章目录前言前面已加学习了下载gitee软件&#xff0c;网址在上一篇文章。在gitee创建账号与仓库。现在来学习如何讲本地项目推送到Gitee远程仓库一、流程总结前言 前面已加学习了下载gitee软件&#xff0c;网址在上一篇文章。在gitee创建账号与仓库。现在来学习如何讲本地项目…

CMake 命令行参数完全指南(5)

​**40. --version**​ ​解释​&#xff1a;显示CMake版本 ​示例​&#xff1a; cmake --version # 输出&#xff1a;cmake version 3.25.2​**41. --warn-uninitialized**​ ​解释​&#xff1a;警告未初始化的变量 ​适用场景​&#xff1a;检测脚本错误 ​示例​&#xf…

基于Python实现生产者—消费者分布式消息队列:构建高可用异步通信系统

深入剖析分布式消息队列的核心原理与Python实现&#xff0c;附完整架构设计和代码实现引言&#xff1a;分布式系统的通信基石在微服务架构和云原生应用普及的今天&#xff0c;服务间的异步通信成为系统设计的核心挑战。当单体应用拆分为数十个微服务后&#xff0c;服务间通信呈…

【大模型核心技术】Agent 理论与实战

一、基本概念 LLM 特性&#xff1a;擅长理解和生成文本&#xff0c;但采用 “一次性” 响应模式&#xff0c;本质上是无记忆的生成模型。Agent 本质&#xff1a;包含 LLM 的系统应用&#xff0c;具备自主规划、工具调用和环境反馈能力&#xff0c;是将 LLM 从 “聊天机器人” 升…

Maven - 依赖的生命周期详解

作者&#xff1a;唐叔在学习 专栏&#xff1a;唐叔的Java实践 标签&#xff1a;Maven依赖管理、Java项目构建、依赖传递性、Spring Boot依赖、Maven最佳实践、项目构建工具、依赖冲突解决、POM文件详解 文章目录一、开篇二、Maven依赖生命周期2.1 依赖声明阶段&#xff1a;POM文…

从零打造大语言模型--处理文本数据

从零打造大语言模型 第 1 章&#xff1a;处理文本数据 章节导读 在把文本投喂进 Transformer 之前&#xff0c;需要两步&#xff1a;① 将字符流切分成离散 Token&#xff1b;② 把 Token 映射成连续向量。 1.1 理解词嵌入&#xff08;Word Embedding&#xff09; 嵌入向量 一…

【Spring】Bean的生命周期,部分源码解释

文章目录Bean 的生命周期执行流程代码演示执行结果源码阅读AbstractAutowireCapableBeanFactorydoCreateBeaninitializeBeanBean 的生命周期 生命周期指的是一个对象从诞生到销毁的整个生命过程&#xff0c;我们把这个过程就叫做一个对象的声明周期 Bean 的声明周期分为以下 …

[spring-cloud: 服务发现]-源码解析

DiscoveryClient DiscoveryClient 接口定义了常见的服务发现操作&#xff0c;如获取服务实例、获取所有服务ID、验证客户端可用性等&#xff0c;通常用于 Eureka 或 Consul 等服务发现框架。 public interface DiscoveryClient extends Ordered {/*** Default order of the dis…

QML 基础语法与对象模型

QML (Qt Meta-Object Language) 是一种声明式语言&#xff0c;专为创建流畅的用户界面和应用程序逻辑而设计。作为 Qt 框架的一部分&#xff0c;QML 提供了简洁、直观的语法来描述 UI 组件及其交互方式。本文将深入解析 QML 的基础语法和对象模型。 一、QML 基础语法 1. 基本对…

HTTPS的概念和工作过程

一.HTTPS是什么HTTPS也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层&#xff08;SSL&#xff09;HTTP协议内容都是按照文本的方式明文传输的&#xff0c;这就导致传输过程中可能出现被篡改的情况最著名的就是十多年前网络刚发展的时期&#xff0c;出现“…

Unity —— Android 应用构建与发布​

文章目录1 ​Gradle模板​​&#xff1a;了解Gradle模板的作用及使用方法&#xff0c;以增强对构建流程的控制。​2 ​Gradle模板变量​​&#xff1a;参考文档——自定义Gradle模板文件中可用的变量列表。2.1 修改Unity应用的Gradle工程文件2.1.1 通过Gradle模板文件2.1.2 导出…

【iOS】strong和copy工作流程探寻、OC属性关键字复习

文章目录前言strong和copy的区别为什么要用copy&#xff1f;什么时候用什么修饰&#xff1f;strong&#xff08;ARC自动管理&#xff09;strong修饰变量的底层流程图底层代码核心实现小结copy底层流程图对比与strong的关键不同之处内部调用关系&#xff08;伪代码&#xff09;小…

程序代码篇---多循环串口程序切换

上位机版&#xff08;Python&#xff09;要实现根据串口接收结果高效切换四个 while 循环函数&#xff0c;我们可以采用状态机模式&#xff0c;配合非阻塞串口读取来设计程序结构。这种方式可以实现快速切换&#xff0c;避免不必要的资源消耗。下面是一个高效的实现方案&#x…