【STL】优先级队列反向迭代器详解

目录

一,栈_刷题必备

二,stack实现

1.什么是容器适配器

2.STL标准库中stack和queue的底层结构 

了解补充:容器——deque 

1. deque的缺陷

2. 为什么选择deque作为stack和queue的底层默认容器

三,queue实现

1. 普通queue 

2,优先级队列(有难度)

<1>. 功能

<2>. 模拟实现

1). 利用迭代器_构造

2).仿函数

sort函数中的仿函数使用理解

四. 反向迭代器(以list为例)

2. 关于迭代器文档一个小细节 

结语


 

一,栈_刷题必备

常见接口: 

stack()     造空的栈
empty()    检测 stack 是否为空
size()        返回 stack 中元素的个数
top()         返回栈顶元素的引用
push()      将元素 val 压入 stack
pop()        stack 中尾部的元素弹出

 简单应用,刷点题:

155. 最小栈

栈的压入、弹出序列_牛客题霸_牛客网

150. 逆波兰表达式求值

二,stack实现

思路:在C语言期间,我们可以通过链表,数组形式实现过stack,而数组形式效率更高,所以stack的实现可以直接复用vector接口,包装成栈先进后出的特性

#pragma once
#include <iostream>
#include <vector>
#include <list>
using namespace std;
namespace my_s_qu
{template <class T >class stack{public:void push_back(const T& x){Data.push_back(x);}void Pop(){Data.pop_back();}T& top(){return Data.back();}const T& top() const {return Data.back();}bool empty() const{return Data.empty();}size_t size() const{return Data.size();}private:vector<T> Data;};
}

很简单不是吗? 到这里并没有结束,我们发现,我们的栈只能通过vector实现。根据c++标准库,我们还差个适配器的实现,换句话说,我们实现的栈不支持容器适配器。 

1.什么是容器适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结), 该种模式是将一个类的接口转换成客户希望的另外一个接口

2.STL标准库中stack和queue的底层结构 

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为 容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用 deque

回到我们栈的实现,我们仅支持vector模式设计,优化为成容器适配器支持所有容器,都能支持栈的先进后出的特性。(在我看来这中思想更为重要)

namespace my_s_qu
{template <class T, class container = deque<T> >  // 添加容器模板{public:void push_back(const T& x){Data.push_back(x);}......
private:container Data;  // 容器换成模板};
}

其中deque又是什么?

了解补充:容器——deque 

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

deque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二 维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

 

那deque是如何借助其迭代器维护其假想连续的结构呢? 

 

1. deque的缺陷

优势: 

与vector比较,deque的 优势是: 头部插入和删除效率高。  不需要搬移元素,效率特别高,而且在 扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较优势支持随机访问。其底层是连续空间, 空间利用率比较高,不需要存储额外字段。

缺陷:  

不适合遍历。
因为在遍历时,deque的迭代器要 频繁的去 计算每个数据的位置,导致 效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list, deque的应用并不多,而目前能看到的一个应用就是, STL用其作为stack和queue的底层数据结构

2. 为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构, 只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对sack和queue默认选择deque作为其底层容器,主要是因为:
1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。

三,queue实现

1. 普通queue 

 queue实现,我们这次以容器适配器进行。

template <class T, class container = deque<T>>class queue{public:void push_back(const T& x){Data.push_back(x);}void Pop(){Data.pop_front();}T& back(){return Data.back();}const T& back()  const{return Data.back();}T& front(){return Data.front();}const T& front() const{return Data.front();}bool empty() const{return Data.empty();}size_t size() const{return Data.size();}private:container Data;};

2,优先级队列(有难度)

简介:

1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 优先级队列底层类似于堆,在堆中可以随时插入元素,并且只能检索 最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出(Pop),其称为优先队列的顶部。
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector
6. 需要 支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

<1>. 功能

priority_queue()/priority_queue(fifirst, last)      构造一个空的优先级队列
empty( )                                                                 检测优先级队列是否为空,是返回true,否则返回false
top( )                                                                      返回优先级队列中最大(最小元素),即堆顶元素
push(x)                                                                  在优先级队列中插入元素x
pop()                                                                      删除优先级队列中最大(最小)元素,即堆顶元素
pop_back()                                                            删除容器尾部元素

<2>. 模拟实现

完成优先级队列,需要用到堆方面的知识,如果堆大家不太熟悉了,建议将堆实现内容复习一遍:

详解树与二叉树的概念,结构,及实现(上篇)_花果山~~程序猿的博客-CSDN博客

 利用堆方面知识,完成最基础的框架:

namespace my_priority_queue
{template <class T, class container = vector<T>> class priority_queue{public:// 自定义类型,不需要给他初始化void ajust_up(size_t child){int parent;while (child > 0){parent = child / 2;if (_pri_queue[child] > _pri_queue[parent])  // 写大堆{std::swap(_pri_queue[child], _pri_queue[parent]);child = parent;parent = child / 2;}else{break;}}}T& top(){return _pri_queue[0];}bool empty(){return _pri_queue.empty();}void push(const T& x){_pri_queue.push_back(x);// 向上调整ajust_up(_pri_queue.size() - 1);}size_t size(){return _pri_queue.size();}void ajust_down(){size_t  parent = 0;size_t child = 2 * parent + 1;while (child < _pri_queue.size()){if (child + 1 < _pri_queue.size() && _pri_queue[child + 1] >  _pri_queue[child]){child++;}if (_pri_queue[parent] < _pri_queue[child]){std::swap(_pri_queue[parent], _pri_queue[child]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){std::swap(_pri_queue[0], _pri_queue[size() - 1]);// 向下调整_pri_queue.pop_back();ajust_down();}private:container _pri_queue;};
}

这里我们会有一个疑问,那我们要构建升序怎么办? 仿函数会解释

1). 利用迭代器_构造

// 自定义类型,不需要给他初始化priority_queue(){}template  <class newiterator>priority_queue(newiterator begin, newiterator end): _pri_queue(){while (begin != end){push(*begin);begin++;}}

2).仿函数

在该场景下,我们的优先级队列已经实现了降序的功能,那我们如何实现升序? 什么你说再写一段,改一下符号??

这里仿函数就得引出了,仿函数,那么它并不是函数,那是什么? (仿函数比如:less, greater)

 仿函数本质是一个类,根据上图中,compare 模板,说明这里的仿函数,就是用来灵活改变大小堆符号的。

我们直接实现结果:

    template <class T>struct less{bool operator()(const T& left, const T& right)const{return left < right;}};template <class T>struct greater{bool operator()(const T& left, const T& right)const{return left > right;}};

 这个两个类都是重载了operator(),因此我们在实例化对象后,使用:

template <class T,class compare = less<T>>

compare   t;

cout <<  t(1, 2) << endl;     

从外表来看,就像一个函数调用,但实际是,一个类的成员函数的调用

t.operator(1,2) 

 这样我们就可以直接改变仿函数的类,来控制大小堆了。 

sort函数中的仿函数使用理解

函数模板中: 

 sort函数作为行参中使用:

  

sort(s.begin, s.end, greater<int> () );   // 函数中是要做参数的,我们加个括号就是一个匿名对象

四. 反向迭代器(以list为例)

如果有小伙伴还学习普通迭代器,请参考这篇文章中的普通迭代器实现。

【STL】list用法&试做_底层实现_花果山~~程序猿的博客-CSDN博客

 参考list源码,这里直接说结果,发现源码通过借用普通迭代器来构造反向迭代器

 直接上代码:

namespace my_list
{template <class T>struct list_node{list_node(const T& data = T()): _data(data), _next(nullptr), _prv(nullptr){}T _data;list_node* _next;list_node* _prv;};template <class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator< T, Ref, Ptr> iterator;Node* _node;list_iterator(Node* node): _node(node){}bool operator!= (const iterator& it){return _node != it._node;}bool operator==(const iterator& it){return _node == it._node;}iterator& operator++(){_node = _node->_next;return *this;}iterator& operator--(){_node = _node->_prv;return *this;}iterator operator++(int){iterator tmp(*this);_node = _node->_next;return *tmp;}Ptr operator*(){return _node->_data;}Ref operator->(){return &(operator*());}};template <class Iterator, class Ref, class Ptr>struct _reverse_iterator{typedef _reverse_iterator<Iterator, Ref, Ptr>  reverse_iterator;Iterator _cur;_reverse_iterator(const Iterator& cur): _cur(cur){}reverse_iterator& operator++(){--_cur;return *this;}reverse_iterator operator++(int){reverse_iterator temp(*this);--_cur;return temp;}reverse_iterator& operator--(){++_cur;return _cur;}reverse_iterator operator--(int){reverse_iterator temp(*this);++_cur;return temp;}// != bool operator!=(const reverse_iterator& end){return _cur != end._cur;}bool operator==(const reverse_iterator&  end){return _cur == end._cur;}// *     Ptr operator*() {auto tmp = _cur;--tmp;return *tmp;}// ->Ref operator->(){return &(operator*());}};template <class T>class list{typedef list_node<T> Node;public:typedef list_iterator<T, T*, T&>  iterator;typedef list_iterator<T, const T*, const T&> const_iterator;typedef _reverse_iterator<iterator, T*, T&> reverse_iterator;typedef _reverse_iterator<const_iterator, const T*, const T&> const_reverse_iterator;reverse_iterator rbegin(){return reverse_iterator(end());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}
..... //list其他成员函数这里就不再赘述了

设计思路比较简单,本质上是复用普通迭代器的函数,其他重载函数思想跟普通函数差不多。但这里也有一个比较艺术性的设计:

 

 

那这里我们来讨论一下,这个反向迭代器是否能给vector使用??  答案是肯定的

看图:

 

结论:反向迭代器:迭代器的适配器。

 

2. 关于迭代器文档一个小细节 

那是不是所有的容器都合适呢? 

不一定,因为容器的普通迭代器最起码要支持++,--接口(比如:foward_list就不支持--,所以其没有反向迭代器)

这里补充一些关于[STL]文档的使用,从迭代器功能角度分为三类:

1. forward_iterator  (单向迭代器)      支持——>  ++              比如: foward_list等等

2. bidirectional_iterator(双向迭代器)   ——>  ++  --          比如: list等

3. radom_access_iterator  (随机迭起器) ——>  ++ --  + -   比如:vector, deque等, 第三中迭代器继承1,2种

那意义又是什么??

意义:就是提示在使用迭代器时,接口会提示你合适的的迭代器类型。

 

 

 

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

gradle 命令行单元测试执行问题

文章目录 问题&#xff1a;命令行 执行失败最终解决方案&#xff08;1&#xff09;ADB命令&#xff08;2&#xff09;Java 环境配置 问题&#xff1a;命令行 执行失败 命令行 执行测试命令 无法使用&#xff08;之前还能用的。没有任何改动&#xff0c;又不能用了&#xff09; …

DC-7靶机

DC-7靶机地址 同样的&#xff0c;把靶机跟kali放在同一网段&#xff0c;&#xff08;NAT模式&#xff09; 主机发现 arp-scan -l端口扫描 nmap -A -T4 -p- 192.168.80.13922端口开始&#xff0c;80端口开启 浏览器先访问一下靶机的80端口 熟悉的Drupal站点 先爆破一下目录…

深入学习 Redis - 事务、实现原理、指令使用及场景

目录 一、Redis 事务 vs MySQL事务 二、Redis 事务的执行原理 2.1、执行原理 2.2、Redis 事务设计这么简单&#xff0c;为什么不涉及成 MySQL 那样强大呢&#xff1f; 三、Redis 事务的使用 3.1、使用场景 3.2、具体演示 开启/执行/放弃事务 watch 监控 watch 实现原理…

【Terraform学习】保护敏感变量(Terraform配置语言学习)

实验步骤 创建 EC2 IAM 角色 导航到IAM 在左侧菜单中&#xff0c;单击角色 。单击创建角色该按钮以创建新的 IAM 角色。 在创建角色部分&#xff0c;为角色选择可信实体类型&#xff1a; AWS 服务 使用案例:EC2 单击下一步 添加权限&#xff1a;现在&#xff0c;您可以看到…

HA3 SQL样本实验:一种混合计算查询的全新样本解决方案

作者&#xff1a;陆唯一(芜霜) HA3&#xff08;对外开源代号&#xff1a;Havenask &#xff09;是阿里智能引擎团队自研的大规模分布式检索系统&#xff0c;广泛应用于阿里内部的搜索业务&#xff0c;是十多年来阿里在电商领域积累下来的核心竞争力产品。Ha3 SQL 是在原有Ha3引…

Promise详细版

promise基础原理到难点分析 常见的Promise的方法解读 扩展async和await深入分析 逐步分析Promise底层逻辑代码 一、Promise基础 1.什么是promise 为了解决回调地狱&#xff1a; //2.设置点击事件btn.onclick function() {//3.创建ajax实例化对象let xhr new XMLHttpRe…

C++/Qt读写ini文件

今天介绍C/Qt读写ini文件&#xff0c;ini文件一般是作为配置文件来使用&#xff0c;比如一些程序的一些默认参数会写在一个ini文件中&#xff0c;程序运行时会进行对应的参数读取&#xff0c;详细可以查看百度ini文件的介绍。https://baike.baidu.com/item/ini%E6%96%87%E4%BB%…

Spring Cloud Gateway

一 什么是Spring Cloud Gateway 网关作为流量的入口&#xff0c;常用的功能包括路由转发&#xff0c;权限校验&#xff0c;限流等。 Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架&#xff0c;定位于取代 Netflix Zuul。相比 Zuul 来说&#xff0c;Spring Clo…

【导出Word】如何使用Java+Freemarker模板引擎,根据XML模板文件生成Word文档(只含文本内容的模板)

这篇文章&#xff0c;主要介绍如何使用JavaFreemarker模板引擎&#xff0c;根据XML模板文件生成Word文档。 目录 一、导出Word文档 1.1、基础知识 1.2、制作模板文件 1.3、代码实现 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;创建Freemarker工具类 &…

W5500-EVB-PICO作为TCP Client 进行数据回环测试(五)

前言 上一章我们用W5500-EVB-PICO开发板通过DNS解析www.baidu.com&#xff08;百度域名&#xff09;成功得到其IP地址&#xff0c;那么本章我们将用我们的开发板作为客户端去连接服务器&#xff0c;并做数据回环测试&#xff1a;收到服务器发送的数据&#xff0c;并回传给服务器…

【分布式系统】聊聊流量和数据调度

对于分布式系统来说&#xff0c;除了监控层面、以及服务治理层面&#xff0c;还有两个层面流量和数据调度。流量调度其实比较好理解&#xff0c;就是用户请求的流量&#xff0c;如何按照一定的策略算法打到不同的机器上。以及如何实现一个高可用、高性能的流量调度平台。而数据…

BM5 合并k个已排序的链表 javascript

描述 合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。 数据范围&#xff1a; 示例1 输入&#xff1a; [{1,2,3},{4,5,6,7}] 返回值&#xff1a; {1,2,3,4,5,6,7}示例2 输入&#xff1a; [{1,2},{1,4,5},{6}] 返回值&#xff1a; {1,1,2,4,5,6}解题思路 利用两个…