[C++历练之路]优先级队列||反向迭代器的模拟实现

W...Y的主页 😊 

代码仓库分享💕


🍔前言:
在C++的宇宙中,优先队列似乎是一座巨大的宝库,藏匿着算法的珍宝。而就在这片代码的天空下,我们不仅可以探索优先队列的神奇,还能够揭开反向迭代器的神秘面纱。让我们一同踏入这个编程的探险之旅,在这里,我们将用C++语言创造出一个能按照优先级排列元素的神奇容器,并且探索反向迭代器的魅力,仿佛是在编码的星空下追逐着闪烁的代码流星。准备好了吗?让我们迈出第一步,开启这段惊险又充满奇迹的模拟之旅。

目录

了解priority_queue

模拟实现priority_queue

构建基本框架

仿函数的介绍以及第三个参数添加

反向迭代器的模板实现


了解priority_queue

1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素

op_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

 优先队列其实就是数据结构中的堆,而我们想要进行其实现必须掌握其模板。

默认情况下,priority_queue是大堆,而第一个模板参数class T就是其对应的数据类型,第二个模板参数是其数据结构的类型,缺省值为vector,所以其默认的结构类型就是数组,不是链式结构类型的堆,如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。第三个模板类型就是一种仿函数,其可以操控其创建的是大堆还是小堆。

所以我们要用堆的思想来模拟实现优先队列!

模拟实现priority_queue

构建基本框架

首先我们可以照猫画虎,仿照其参数模板进行仿写:

#pragma once
#include<vector>
#include<iostream>
#include<vector>
#include<deque>
#include<stdbool.h>
using namespace std;
namespace why
{template<class T, class Container = vector<T>>class priority_queue{public:void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};

我们将基本函数框架打好,将优先队列的基本函数接口完善,这些都是我们复用的vector、list、deque等接口,可以直接从STL中直接调用。 

注意:这里我们在函数模板中未加入第三个参数进行参数,这里我们在最后实现。原模板接口的缺省默认参数为less<T>,是构建大堆的,所以我们模拟中是先建立大堆。、

往vector中push数据时就要建立大堆进行排序,pop数据得使用向下调整对堆中的数据重新排序成为大堆,所以建立大堆就是使用数据结构中的向上调整函数进行操作,而pop数据是用向下调整的方法进行。

如果对堆这一块的不太了解,可以一下文章:

堆的基本实现——数据结构icon-default.png?t=N7T8https://blog.csdn.net/m0_74755811/article/details/132794715?spm=1001.2014.3001.5502向上调整:

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[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

向下调整:

void adjust_down(size_t parent)
{//Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && _con[child] < _con[child + 1])//if(child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}if(_con[child] > _con[parent])//if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

仿函数的介绍以及第三个参数添加

在C++中,仿函数(Functor)是一种重载了函数调用操作符 operator() 的对象。它实际上是一个类或者结构体,通过重载 operator(),使得该对象可以像函数一样被调用。仿函数可以像函数一样接受参数,并返回结果,同时可以包含状态信息,因此它们在C++中被广泛用于实现函数对象,作为算法的参数传递,或者用于定义自定义的操作。

通过仿函数,可以实现自定义的比较、排序、转换或者其他操作,这些操作可以被算法直接使用,例如在标准库中的排序算法 std::sort、查找算法 std::find,或者容器类中的自定义排序规则等。使用仿函数可以提供更大的灵活性,使得算法能够适应不同的需求。

下面是一个简单的示例,展示了一个自定义的仿函数用于比较两个整数的大小:

#include <iostream>// 定义一个比较器仿函数
struct Compare {bool operator()(int a, int b) const {return a < b; // 自定义的比较规则:a < b}
};int main() {Compare cmp; // 创建比较器对象int x = 5, y = 10;if (cmp(x, y)) {std::cout << "x is less than y." << std::endl;} else {std::cout << "x is greater than or equal to y." << std::endl;}return 0;
}

在这个示例中,Compare 结构体重载了 operator(),定义了一个比较规则,判断第一个参数是否小于第二个参数。然后在 main 函数中,创建了一个 Compare 类型的对象 cmp,并使用它进行比较操作。

因此,仿函数是C++中的一种强大机制,可以扩展函数的行为,提供更灵活的功能,并允许开发者以更抽象的方式定义特定操作。

所以我们可以使用仿函数针对第三个参数。

priority_queue函数的第三个默认缺省参数为less<T>,如果我们传greater<T>才可以创建小堆。而我们模拟的函数中创建大小堆只不过是将其比较符号进行转换即可。所以我们就可以使用仿函数创建两个不同类型的进行调用。

#pragma once
#include<vector>
#include<iostream>
#include<vector>
#include<deque>
#include<stdbool.h>
using namespace std;
namespace why
{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{public: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[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){//if (child + 1 < _con.size() && _con[child] < _con[child + 1])if(child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}//if(_con[child] > _con[parent])if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};

这样我们只需要在模拟函数中创建一个模板变量即可在函数中进行调用。

反向迭代器的模板实现

在STL中的所有容器里都有迭代器与反向迭代器,而在每个容器的模拟实现中我们也将其进行复现。string、vector中的迭代器都可以类似与指针,因为其底层的存储物理空间是连续的,我们可以很好的进行重定义使用。但是list却不行,因为空间是不连续的,所以我们得重新定义封装出一个类迭代器的重定义,将其运算符进行重载成合理的进行使用。

而反向迭代器中我们可以将list中封装的迭代器进行复制粘贴修改,就可以正确使用。

rend指向头节点,而rbegin指向_head->_prev节点,也就是尾节点即可。 

template<class T, class Ref, class Ptr>
struct __list_reverse_iterator
{typedef list_node<T> node;typedef __list_reverse_iterator<T, Ref, Ptr> self;node* _node;__list_reverse_iterator(node* n):_node(n){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_prev;return *this;}self operator++(int){self tmp(*this);_node = _node->_prev;return tmp;}self& operator--(){_node = _node->_next;return *this;}self operator--(int){self tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};

我们只需要将++、--运算符进行重载即可。将++指向当前节点的_prev节点,而--指向当前节点的_next节点。

那我们看一下STL-list中的反向迭代器是怎么写的:

class reverse_bidirectional_iterator {typedef reverse_bidirectional_iterator<_BidirectionalIterator, _Tp, _Reference, _Distance>  _Self;
protected:_BidirectionalIterator current;
public:typedef bidirectional_iterator_tag iterator_category;typedef _Tp                        value_type;typedef _Distance                  difference_type;typedef _Tp*                       pointer;typedef _Reference                 reference;reverse_bidirectional_iterator() {}explicit reverse_bidirectional_iterator(_BidirectionalIterator __x): current(__x) {}_BidirectionalIterator base() const { return current; }_Reference operator*() const {_BidirectionalIterator __tmp = current;return *--__tmp;}
#ifndef __SGI_STL_NO_ARROW_OPERATORpointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */_Self& operator++() {--current;return *this;}_Self operator++(int) {_Self __tmp = *this;--current;return __tmp;}_Self& operator--() {++current;return *this;}_Self operator--(int) {_Self __tmp = *this;++current;return __tmp;}
};

STL中的反向迭代器是封装了正向迭代器,构造一个反向迭代器了。正向迭代器的++就是反向迭代器的--,而正向迭代器的--就是反向迭代器的++。

注意:STL源码中的复用代码rbegin()与rend()的源码为:

直接返回的是其正向迭代器的begin()与end(),所以其解引用的内容就要发生变化:

其结构就不同:

与原来的结构是不同的。

我们可以自己封装一个类进行使用:

#pragma once
namespace why
{template<class Iterator, class Ref>struct ReverseIterator{typedef ReverseIterator<Iterator, Ref> Self;Iterator _cur;ReverseIterator(Iterator it):_cur(it){}Ref operator*(){Iterator tmp = _cur;--tmp;return *tmp;}Self& operator++(){--_cur;return *this;}Self& operator--(){++_cur;return *this;}bool operator!=(const Self& s){return _cur != s._cur;}};
}

 但是这样复用正向迭代器与刚才的写法所表达的写法是一样的,为什么还要这样单独创建一个类呢?因为list的正向迭代器就是进行封装的,可以复用。但是string、vector的正向迭代器就是指针就不能进行此操作了,所以我们必须复用。


总结:

在这段代码的奇妙旅程中,我们成功地创造了一个C++中的优先队列,仿佛编织了一个可以按照优先级排序元素的魔法网。这个队列不仅仅是一段代码,更是算法的交响乐,奏响着排序、插入、删除的优美旋律。而更加令人惊叹的是,我们在这个编码的仙境中,还揭开了反向迭代器的神秘面纱,为我们的容器增添了一抹独特的色彩。

通过这个模拟实现,我们深入理解了C++中优先队列的本质,并感受到了反向迭代器的便利之处。这不仅是一次代码之旅,更是对数据结构和算法的深刻思考,是对编程艺术的一次追求和探索。

或许,在未来的编程征途中,你会在实际项目中运用这些知识,创造出更为强大、高效的代码。无论何时何地,优先队列和反向迭代器的魔法都将伴随着你,成为解决问题的得力工具。

让我们怀着对编码奇迹的敬畏之心,结束这段代码的冒险。愿你的代码之路充满创造与探索,愿你的算法之舞永远翩翩起舞。编码的世界里,冒险永不止步,期待着你下一次的代码奇迹。

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

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

相关文章

【【Linux系统下常用指令学习 之 二 】】

Linux系统下常用指令学习 之 二 文件查询和搜索 文件的查询和搜索也是最常用的操作&#xff0c;在嵌入式 Linux 开发中常常需要在 Linux 源码文件中查询某个文件是否存在&#xff0c;或者搜索哪些文件都调用了某个函数等等。 1、命令 find find 命令用于在目录结构中查找文件…

BUUCTF [ACTF新生赛2020]outguess 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 得到的 flag 请包上 flag{} 提交。 密文&#xff1a; 下载附件&#xff0c;得到一堆文件。 解题思路&#xff1a; 1、根据题目和flag.txt文件提示&#xff0c;猜测为outguess隐写。 outguess下载安装 kail 终端命…

数字乡村:科技赋能农村产业升级

数字乡村&#xff1a;科技赋能农村产业升级 数字乡村是指通过信息技术和数字化手段&#xff0c;推动农业现代化、农村经济发展和农民增收的一种新模式。近年来&#xff0c;随着互联网技术的飞速发展&#xff0c;数字乡村开始在全国范围内迅速兴起&#xff0c;为乡村经济注入了新…

leedcode 刷题 - 除自身以外数组的乘积 - 和为 K 的子数组

I238. 除自身以外数组的乘积 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在…

AdaBoost提升分类器性能

目录 AdaBoost算法原理 AdaBoost工作详情 初始权重分配 第一轮 第二轮 后续轮次 最终模型 AdaBoost的API解释 AdaBoost 对房价进行预测 AdaBoost 与决策树模型的比较 结论 AdaBoost算法原理 在数据挖掘中&#xff0c;分类算法可以说是核心算法&#xff0c;其中 Ada…

gitee推荐-PHP面试准备的资料

该内容为giee项目。PHP-Interview: 这个项目是自己准备PHP面试整理的资料。包括PHP、MySQL、Linux、计算机网络等资料。方便自己以后查阅&#xff0c;会不定期更新&#xff0c;欢迎提交pr&#xff0c;如果错误&#xff0c;请指出&#xff0c;谢谢 在线预览地址&#xff1a;Intr…

【LeetCode刷题笔记】DFSBFS(三)

图的基础知识 邻接矩阵是一个二维表,其中横纵坐标交叉的格子值为 1 的表示这两个顶点是连通的,否则是不连通的。

NVM得介绍和详细使用教程

NVM​​​​​​​&#xff08;Node Version Manager&#xff09;是一个用于管理多个Node.js版本的工具。它允许您在同一台计算机上轻松地切换和管理不同的Node.js版本。以下是NVM的介绍和详细使用教程&#xff1a; 安装NVM&#xff1a; 首先&#xff0c;您需要在计算机上安装N…

C#串口通信从入门到精通(27)——高速通信下解决数据处理慢的问题(20ms以内)

前言 我们在开发串口通信程序时,有时候会遇到比如单片机或者传感器发送的数据速度特别快,比如10ms、20ms发送一次,并且每次发送的数据量还比较大,如果按照常规的写法,我们会发现接收的数据还没处理完,新的数据又发送过来了,这就会导致处理数据滞后,软件始终处理的不是…

python树的双亲存储结构

这种存储结构是一种顺序存储结构&#xff0c;采用元素形如“[结点值&#xff0c;双亲结点索引]”的列表表示。通常每个结点有唯一的索引(或者伪地址&#xff09;,根结点的索引为0&#xff0c;它没有双亲结点&#xff0c;其双亲结点的索引为-1。例如&#xff0c;所示的树对应的双…

123. 股票买卖的最佳时机III(2次交易)

题目 题解 class Solution:def maxProfit(self, prices: List[int]) -> int:N len(prices)# 状态定义 dp[i][j][k]代表在第i天&#xff0c;被允许完成j次交易时&#xff0c;持有或者不持有的最大利润。k0代表不持有&#xff0c;k1代表持有dp [[[0 for k in range(2)] for…

医学生秋招攻略,面试时一定要注意这些方面!

医学生别拖了&#xff0c;今年秋招已经过去一波热度了&#xff0c;赶早不赶晚&#xff01;在筹备第二轮秋招以及明年的春招的医学生一定要注意以下事项。 1.清晰目标 搜集秋招讯息 一定要早点多做准备&#xff0c;想清楚未来的目标&#xff0c;是继续深造还是就业做医生或者是…

【开源】基于Vue.js的用户画像活动推荐系统

项目编号&#xff1a; S 061 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S061&#xff0c;文末获取源码。} 项目编号&#xff1a;S061&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 兴趣标签模块2.3 活…

[Android]使用Git将项目提交到GitHub

如果你的Mac还没有安装Git&#xff0c;你可以通过Homebrew来安装它&#xff1a; brew install git 方式一&#xff1a;终端管理 1.创建本地Git仓库 在项目的根目录下&#xff0c;打开终端&#xff08;Terminal&#xff09;并执行以下命令来初始化一个新的Git仓库&#xff1…

vue3-组件传参及计算属性

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue3-组件传参及计算属性 目录 vue3中的组件传参 1、父传子 2、子传父 toRef 与 toRefs vue3中…

实例讲解:在3dMax中如何使用python脚本?

如果你是Python或Maxscript的新手&#xff0c;你现在可以跟着这篇文章开始做一些代码了&#xff0c;本文将让我们从非常基本的东西开始学习。 如何在3dmax中获取选定的节点并打印出它们的名称&#xff1f;所有场景对象如何&#xff1f;我们直接看代码&#xff1a; import MaxP…

Word/PPT/PDF怎么免费转为JPG图片?

1、打开金鸣表格文字识别网站。 2、点击导航条上的“软件下载” 3、安装并打开金鸣表格文字识别软件。 4、点击顶部导航栏的“文件转图片”。 5、选择需要转换成图片的文件&#xff08;支持Word/PPT/PDF&#xff09;. 6、点“打开”程序将自动分页转换为图片。

【论文阅读笔记】Smil: Multimodal learning with severely missing modality

Ma M, Ren J, Zhao L, et al. Smil: Multimodal learning with severely missing modality[C]//Proceedings of the AAAI Conference on Artificial Intelligence. 2021, 35(3): 2302-2310.[开源] 本文的核心思想是探讨和解决多模态学习中的一个重要问题&#xff1a;在训练和测…

JS中的OOP

JS中的OOP OOP 为我们解决了什么问题&#xff1f;想象一下&#xff0c;我们希望为教师提供一个平台&#xff0c;每位注册的教师都可以提交分数&#xff0c;并为课程分配作业和其他内容。 如果有一个地方&#xff08;在本例中是一个对象&#xff09;&#xff0c;可以访问所有教…

Python编写的爬虫为什么受欢迎?

每每回想起我当初学习python爬虫的经历&#xff0c;当初遇到的各种困难险阻至今都历历在目。即便当初道阻且长&#xff0c;穷且益坚&#xff0c;我也从来没有想过要放弃。今天我将以我个人经历&#xff0c;和大家聊一聊有关Python语音编写的爬虫的事情。谈一谈为什么最近几年py…