【C++】优先级队列priority_queue模拟实现仿函数

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能手撕仿函数模拟

> 毒鸡汤:你活得不快乐的原因是:既无法忍受目前的状态,又没能力改变这一切。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言

我们在vector讲解中已经了解到了priority_queue,只能说是浅谈,priority_queue底层到底是个啥勒?今天带大家揭晓它的面纱。

⭐主体

这里就创建两个文件priority_queue.h(头文件),test.cpp(测试代码文件)

咱们按照下面图解来学习今天的内容:

🌙什么是priority_queue

优先级队列priority_queue,即数据结构中的堆,堆是一种通过使用数组来模拟实现特定结构二叉树的二叉树的数据结构,根据父亲节点与孩子节点的大小关系,可以将堆分为大堆和小堆:

大堆:所有父亲节点的值都大于或等于它的孩子节点的值。
小堆:所有父亲节点的值都小于或等于它的孩子节点的值。
在C++ STL中,priority_queue的声明为:template <class T, class Container = vector<T>, class Compare = std::less<T>>  class priority_queue;

其中,每个模板参数的含义为:

  • T:优先级队列中存储的数据的类型
  • Container:用于实现优先级队列的容器,默认为vector
  • Comapre:比较仿函数,用于确定是建大堆还是建小堆。Compare默认为std::less<T>,建大堆,如果要建小堆,需要显示传参std::greater<T>,同时还有显示的声明容器类型。

🌙priority_queue常见接口的使用

  1. 优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素是所有元素中最大的。
  2. 优先级队列的底层是用堆进行实现的,大根堆的堆顶是最大的。
  3. 标准容器vector和queue都满足以上要求,如果没有特定要求,默认使用vector作为底层容器类。
  4. 需要支持随机访问迭代器,保证内部始终保持堆结构。容器适配器在需要的时候调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
  5. 优先级队列的底层容器可以使任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
  • empty():检测容器是否为空。
  • size():返回容器有效元素个数。
  • front():返回容器第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素

使用priority_queue:

#include<iostream>
#include<queue>
#include<functional>int main()
{std::priority_queue<int> maxHeap;   //建大堆int data[10] = { 56,12,78,23,14,34,13,78,23,97 };//让arr中的数据依次入大堆for (int i = 0; i < 10; ++i){maxHeap.push(data[i]);}std::cout << maxHeap.empty() << std::endl;  //判空 -- 0std::cout << maxHeap.size() << std::endl;   //获取堆中数据个数 -- 10//依次提取大堆堆顶数据并打输出while (!maxHeap.empty()){//97 78 78 56 34 23 23 14 13 12std::cout << maxHeap.top() << " ";maxHeap.pop();}std::cout << std::endl;std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;  //建小堆//让arr中的数据依次入小堆for (int i = 0; i < 10; ++i){minHeap.push(data[i]);}//依次提取堆顶数据并打输出while (!minHeap.empty()){//12 13 14 23 23 34 56 78 78 97std::cout << minHeap.top() << " ";minHeap.pop();}std::cout << std::endl;return 0;
}

运行结果:

🌙priority_queue的模拟实现

💫仿函数的实现

仿函数是使用struct定义的类对象,通过重载操作符(),即operator()(参数列表)实现类似函数的功能。在类中定义运算符()的重载函数,通过类对象,来使调用运算符重载函数的语法,仿函数的调用语法为:类对象名称(参数列表)

在priority_queue的构造函数中,就经常使用less和greater两个仿函数,less和greater都是C++标准库中给出的判断两数之间大小关系的仿函数,他们被包含在头文件<functional>中:

  • less:给两个操作数,判断前者是否小于后者。
  • greater:给两个操作数,判断前者是否大于后者。

代码实现:

// 仿函数
template<class T>
struct less
{bool operator()(const T& a, const T& b){return a < b;}
};
template<class T>
struct greater
{bool operator()(const T& a, const T& b){return a > b;}
};

💫构造函数的实现

构造函数有两种重载形式:

  • (1)构造空堆,这时构造函数无需额外编写代码进行任何工作,容器成员和_con和类对象_comp都会被调用他们的默认构造函数被创建出来。
  • (2)通过迭代器区间进行构造,此时先通过迭代器区间构造容器对象,然后执行向下调整建堆操作。

向下调整函数AdjustDown需要有2个参数,分别为:堆中数据个数n和开始向下调整的父亲节点下标parent,其中还会用到类的成员(容器和用于比较的对象),AdjustDown执行的操作依次为(以建大堆为例):

  1. 根据父亲节点的下标,获取其左孩子节点的下标child。
  2. 判断孩子节点下标是否越界,如果越界(child>=n),则函数终止。
  3. 判断左孩子节点和右孩子节点那个较大,如果右孩子节点值较大,则将child更新为右孩子节点下标。
  4. 判断父亲节点是否小于较大的孩子节点,如果小于,则交换父子节点值,然后将父节点更新为孩子节点,然后回到1继续执行向下调整操作,如果大于或等于,则终止向下调整操作。

注意:对于开始执行向下操作的父节点parent,一定要保证它的左子树和右子树都为大或小堆。

构造函数代码实现
// 默认构造函数
priority_queue()
{}
// 通过迭代器区间的构造函数
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last):_con(first, last)
{size_t lastchild = size() - 1;size_t parent = (lastchild - 1) / 2;for (size_t i = parent; i >= 0; i--){AdjustDown(i);}
}
向下调整代码实现
void AdjustDown(size_t parent)
{size_t child = parent * 2 + 1;while (child < size()){//if (child + 1 < size() && _con[child + 1] > _con[child])if (child + 1 < size() && com(_con[child], _con[child + 1]))++child;//if (_con[child] > _con[parent])if (com(_con[parent], _con[child]))swap(_con[parent], _con[child]);elsebreak;parent = child;child = parent * 2 + 1;}
}

💫插入数据函数的实现

向堆中插入数据需要两步操作:先向容器中尾插数据,然后调用AdjustUp函数上调整数据。

向上调整函数AdjustUp执行的操作流程为:(建大堆为例)

  1. 根据开始执行向上调整的孩子节点下标,计算出其父亲节点下标。
  2. 判断孩子节点下标是否>0,如果是,继续执行向上调整操作,否则终止函数。
  3. 判断孩子节点值是否大于父亲节点,如果大于,交换父子节点值,然后更新孩子节点为当前父亲节点,根据更新后孩子节点下标计算父亲节点下标,然后回到1继续执行向上调整操作。如果孩子节点值小于或等于父亲节点值,那么终止向上调整操作。

向堆中插入数据函数
void push(const T& x)
{_con.push_back(x);AdjustUp(size() - 1);
}
向上调整操作函数
void AdjustUp(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){//if (_con[child] > _con[parent])->父亲小于孩子就调整if (com(_con[parent], _con[child]))//{swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}
}

💫删除堆顶数据函数的实现

如果直接将除堆顶之外的全部数据向前移动一个单位,那么数组中剩余的数据大概率无法满足堆的结构要求,重新再建堆效率过低。那么,就需要一些额外的技巧来解决问题:

  • 交换堆顶数据和数组中最后一个数据。
  • 将数组中的最后一个数据删除。
  • 以当前根节点为起始父亲节点,执行向下调整操作,使数组中的数据重新满足堆的结构。
void pop()
{swap(_con[size() - 1], _con[0]);_con.pop_back();AdjustDown(0);
}

💫其它函数功能实现

size_t size() const
{return _con.size();
}
T& top()
{return _con[0];
}
bool empty()
{return _con.empty();
}

🌙priority_queue的完整代码

#include <iostream>
#include <vector>
using namespace std;namespace lyk
{// 仿函数template<class T>struct less{bool operator()(const T& a, const T& b){return a < b;}};template<class T>struct greater{bool operator()(const T& a, const T& b){return a > b;}};// container 容器 ,compare 用于调用比较函数的类对象template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{private:Compare com;void AdjustUp(size_t child){size_t parent = (child - 1) / 2;while (child > 0){//if (_con[child] > _con[parent])->父亲小于孩子就调整if (com(_con[parent], _con[child]))//{swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}void AdjustDown(size_t parent){size_t child = parent * 2 + 1;while (child < size()){//if (child + 1 < size() && _con[child + 1] > _con[child])if (child + 1 < size() && com(_con[child], _con[child + 1]))++child;//if (_con[child] > _con[parent])if (com(_con[parent], _con[child]))swap(_con[parent], _con[child]);elsebreak;parent = child;child = parent * 2 + 1;}}public:// 默认构造函数priority_queue(){}// 通过迭代器区间的构造函数template<class InputIterator>priority_queue(InputIterator first, InputIterator last):_con(first, last){size_t lastchild = size() - 1;size_t parent = (lastchild - 1) / 2;for (size_t i = parent; i >= 0; i--){AdjustDown(i);}}void push(const T& x){_con.push_back(x);AdjustUp(size() - 1);}void pop(){swap(_con[size() - 1], _con[0]);_con.pop_back();AdjustDown(0);}size_t size() const{return _con.size();}T& top(){return _con[0];}bool empty(){return _con.empty();}private:Container _con;};void priority_test1(){priority_queue<int> pq;pq.push(40);pq.push(30);pq.push(56);pq.push(26);pq.push(45);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;}void priority_test2(){priority_queue<int, vector<int>, greater<int>> pq;pq.push(40);pq.push(30);pq.push(56);pq.push(26);pq.push(45);cout << "greater<int>建小堆-->  ";while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;priority_queue<int> pq2;pq2.push(40);pq2.push(30);pq2.push(56);pq2.push(26);pq2.push(45);cout << "默认less<int>建大堆-->  ";while (!pq2.empty()){cout << pq2.top() << " ";pq2.pop();}cout << endl;}
}

💫运行结果

🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

【c++】stack和queue模拟实现

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能手撕stack和queue模拟 > 毒鸡汤&#xff1a;…

基础!!!吴恩达deeplearning.ai:卷积层

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 文章目录 回顾——密集层 Dense Layer卷积层 Convolutional Neural Network定义优势具体说明心电图卷积层搭建 到目前为止&#xff0c;你使用的所有神经网络层都是密集层类型&#xff0c;这…

ICVQUANTUMCHINA报告:《2024全球量子计算产业发展展望》

2月20日&#xff0c;《2024量子计算产业发展展望》的中文版报告通过光子盒官方平台发布&#xff0c;英文版报告通过ICV官方平台发布。 英文版报告获取地址&#xff1a; https://www.icvtank.com/newsinfo/897610.html 在过去的一年里&#xff0c;光子盒与您一同见证了全球量子…

幻兽帕鲁专用服务器搭建之Linux部署配置教程

大家好我是飞飞&#xff0c;上一期我分享了Windows系统的幻兽帕鲁服务器搭建教程。因为幻兽帕鲁这游戏对服务器的配置有一定的要求&#xff0c;很多小伙伴就寻思用Linux系统搭建占用会不会小一点&#xff1f;有计算机基础的小伙伴都知道Linux系统和Windows系统相比&#xff0c;…

探索前景:机器学习中常见优化算法的比较分析

目录 一、介绍 二、技术背景 三、相关代码 四、结论 一、介绍 优化算法在机器学习和深度学习中至关重要&#xff0c;可以最小化损失函数&#xff0c;从而改善模型的预测。每个优化器都有其独特的方法来导航损失函数的复杂环境以找到最小值。本文探讨了一些最常见的优化算法&…

前端Ajax获取当前外网IP地址并通过腾讯接口解析地理位置

目录 一、获取访问端IP地址 二、可用的IP获取接口 1、韩小韩IP获取接口&#xff1a; 2、ipify API 附3、失败的太平洋接口 三、腾讯位置服务-IP位置查询接口 一、获取访问端IP地址 原计划使用后端HttpServletRequest 获取访问端的IP地址&#xff0c;但在nginx和堡垒机等阻…

Python多功能课堂点名器、抽签工具

一、问题缘起 去年&#xff0c;ChatGPT浪潮袭来&#xff0c;我懂简单的Python基础语法&#xff0c;又有一些点子&#xff0c;于是借助于人工智能问答工具&#xff0c;一步一步地制作了一个点名器&#xff0c;也可以用于抽签。当时&#xff0c;我已经设计好页面和基础的功能&am…

Leetcode 第 385 场周赛题解

Leetcode 第 385 场周赛题解 Leetcode 第 385 场周赛题解题目1&#xff1a;3042. 统计前后缀下标对 I思路代码复杂度分析 题目2&#xff1a;3043. 最长公共前缀的长度思路代码复杂度分析 题目3&#xff1a;3044. 出现频率最高的质数思路代码复杂度分析 题目4&#xff1a;3045. …

【EFK】基于K8S构建EFK+logstash+kafka日志平台

基于K8S构建EFKlogstashkafka日志平台 一、常见日志收集方案1.1、EFK1.2、ELK Stack1.3、ELK filbeat1.4、其他方案 二、EFK组件介绍2.1、Elasticsearch组件2.2、Filebeat组件【1】 Filebeat和beat关系【2】Filebeat是什么【3】Filebeat工作原理【4】传输方案 2.3、Logstash组件…

本届挑战赛季军方案:基于图网络及LLM AGENT的微服务系统异常检测和根因定位方法

aiboco团队荣获本届挑战赛季军。该团队来自亿阳信通。 方案介绍 本届挑战赛采用开放式赛题&#xff0c;基于建行云龙舟运维平台的稳定性工具和多维监控系统&#xff0c;模拟大型的生活服务APP的生产环境&#xff0c;提供端到端的全链路的日志、指标和调用链数据。参赛队伍在组…

操作系统:IO管理概述

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 &#x1f3c6;&#x1f3c6;本文完整PDF源文件请翻阅至文章底部下载。&#x1f3c6;&#x1f3c6; 1 I/O设备的基本概念与分类1.1 总览1.2 什么是IO设备1.3 IO设备的分类1…

yolov8涨点技巧,添加SwinTransformer注意力机制,提升目标检测效果

目录 摘要 SwinTransformer原理 代码实现 YOLOv8详细添加步骤 ymal文件内容 one_swinTrans three_swinTrans 启动命令 完整代码分享 摘要 Swin Transformer通过引入创新的分层注意力机制展现了其架构的独特性&#xff0c;该机制通过将注意力区域划分为块并在这些块内执…