C++:Stack和Queue的模拟实现

                                                    创作不易,感谢三连! 

一、容器适配器

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

      就如同是电源适配器将不适用的交流电变得适用一样,模板 B 将不适合直接拿来用的模板 A 变得适用了,因此我们可以将模板 B 称为 B 适配器。 容器适配器也是同样的道理,简单的理解容器适配器,其就是将不适用的序列式容器(包括 vector、deque 和 list)变得适用。 容器适配器的底层实现和模板 A、B 的关系是完全相同的,即通过封装某个序列式容器,并重新组合该容器中包含的成员函数,使其满足某些特定场景的需要。

二、deque的简单了解

      我们知道,vector支持下标的随机访问,但是头部和中部插入删除效率低下,且需要频繁扩容,而list虽然任意位置插入删除高效,但是不支持随机访问,所以就有人在思考,能否找到一种数据结构可以替代他俩呢??于是就有了双端队列这个数据结构,但实际上双端队列并无法替代vector和list,并且后来成为了最适合stack和queue的底层容器,这就是典型的相当皇上没当成,却成了丫鬟。

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

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

     双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,重任落在了deque的迭代器身上。

下面我们进行总结:

我们可以看到:

1、与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

2、与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

3、但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list。

4、deque的应用并不多,但我们发现他的头插头删和尾插尾删的效率特别高,所以目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

5、还有第二个缺陷就是:

(1)如果我们假定buff每个数组大小一样大,那么随机访问的效率就会高,因为我们减掉该层的当前元素,然后/10可以跳层,再%10就可以跳到该层对应的位置,但是需要中间的插入和删除的话,那么为了维持每层的数组大小,就可能需要挪动一堆数据。

(2)如果我们假定每个buff数组的大小不一样大,那么如果中间的插入删除就不需要挪数据了,效率会提高,但是随机访问的效率就会变低了,因为不知道每层有多少元素,所以无法直接跳层,而是要一个个去遍历才能访问到某个元素。

     因此各有利弊不可兼得,在SGI版本下选择的是buff数组固定大小,所以他的迭代器设置得非常复杂。

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

三、Stack介绍

Stack文档介绍

1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其只能从容器的一端进行元素的插入与提取操作。

2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作

4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

四、Queue介绍

Queue文档介绍

1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。

2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。

3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列

4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

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

      stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

所以结合了deque的优点,而完美的避开了其缺陷。

六、适配器模式下的stack模拟实现

using namespace std;
namespace cyx
{template<class T,class Container=deque<T>>// 可以用vectoe list deque作为适配器class stack{public:bool empty() const{return _con.empty();}size_t size() const{return _con.size();}const T& top() const{return _con.back();}T& top(){return _con.back();}void push(const T&val) {_con.push_back(val);}void pop(){_con.pop_back();}void swap(stack<T> &s){_con.swap(s._con);}private:Container _con;//适配器};

七、适配器模式下的queue模拟实现

namespace cyx
{template<class T,class Container=deque<T>>//该适配器可以用list deque  如果是用vector不太合适,因为不支持头删class queue{public:bool empty() const{return _con.empty();}size_t size(){return _con.size();}T& front(){return _con.front();}const T& front() const {return _con.front();}T& back(){return _con.back();}const T& back() const{return _con.back();}void push(const T&val){_con.push_back(val);}void pop(){_con.pop_front();}void swap(queue<T> &q){_con.swap(q._con);}private:Container _con;};

八、遍历的一般形式 

stack

queue

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

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

相关文章

redis中的zset的原理

一、zset有序集合的原理 如果有序集合元素个数少于128个且元素值小于64字节&#xff0c;使用压缩列表&#xff08;新版本已经废弃压缩列表改用listpack数据结构了&#xff09; 如果不满足上述条件&#xff0c;采用跳表作为redis的底层数据结构 二、压缩列表 1.由连续内存块组…

k8s常用命令大全

k8s常用的命令 下面是一些常用的Kubernetes&#xff08;K8s&#xff09;命令&#xff0c;以及它们的简要说明。这些命令可以帮助您管理和操作Kubernetes集群中的资源。 集群管理命令&#xff1a; kubectl cluster-info: 显示集群的基本信息。 kubectl config use-context &l…

Doris-数据分区

数据分区&#xff1a;即将大表划分为小表&#xff0c;数据分区主要有两个级别&#xff1a;Partition和Bucket&#xff08;Tablet&#xff09;。 Partition&#xff1a;逻辑分区&#xff0c;按照一定规则将表按照行进行划分&#xff0c;每个部分就是一个Partition。 Bucket&…

Java开发与配置用到的各类中间件官网

开发配置时用到了一些官网地址&#xff0c;记录一下。 activemq 官网&#xff1a;ActiveMQ elk 官网&#xff1a;Elasticsearch 平台 — 大规模查找实时答案 | Elastic nginx 官网&#xff1a;nginx maven 官网&#xff1a;Maven – Welcome to Apache Maven nexus 官网&a…

MySQL-----视图

一 视图 ▶ 介绍 视图view是一个虚拟表&#xff0c;非真实存在&#xff0c;其本质是根据SQL语句获取动态的数据集&#xff0c;并为其命名&#xff0c;用户使用时只需使用视图名称即可获取结果集&#xff0c;并可以将其当作表来使用。 数据库中存放了视图的定义&…

修复 因 fstab 中UUID 错误导致系统无法正常工作的问题

操作系统&#xff1a; PVE 8.0 /debian 12 &#xff08;bookworm&#xff09; 问题症状&#xff1a;可以正常启动进入系统&#xff0c;但是系统盘以只读方式挂载 问题原因&#xff1a;/etc/fstab 中引导区的UUID 被错误修改导致 解决方法&#xff1a; 重启系统&#xff0c;在…

CPU设计实战-加载和存储指令(2)

目录 一 ll和sc指令说明 二 ll和sc指令的实现 1 llbit寄存器 2 译码阶段 3 访存阶段 4 Load相关问题 5 流水线在取指阶段暂停 本章介绍两个比较特殊的加载存储指令ll和sc&#xff0c;这两个指令的存在用于实现信号量机制。 信号量机制&#xff1a;在多线程中为了保证某个…

项目的搭建与配置

vue create calendar_pro 选择如下配置选项 安装 vue3 支持 vue add vue-next package.json 关闭 eslint 检测。 vue.config.js 配置跨域同源策略。 const { defineConfig } require(vue/cli-service) module.exports defineConfig({transpileDependencies: true,devServe…

MyBatis-Plus如何娴熟运用乐观锁

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MyBatis-Plus如何娴熟运用乐观锁 前言乐观锁的基本概念基本概念和原理&#xff1a;为何乐观锁是解决并发问题的有效手段&#xff1a; MyBatis-Plus中乐观锁的支持1. Version 注解&#xff1a;2. 配置乐…

深入浅出Redis(八):Redis的集群模式

引言 Redis是一款优秀的键值对、内存非关系型数据库&#xff0c;单机节点下的Redis存在无法保证高可用、容量不足等问题 上篇文章介绍的哨兵主要能够保证主从架构下Redis的可用性&#xff0c;但是仍然存在容量不足、推举新的主节点时不能访问Redis的问题&#xff0c;集群可水…

开发Chrome扩展插件

1.首先开发谷歌chrome扩展插件&#xff0c;没有严格的项目结构目录&#xff0c;但是需要保证里面有一个mainfest.json文件 (必不可少的文件)。在这个文件里有三个属性必不可少&#xff1a;name、version、mainfest_version&#xff1b; // 清单文件的版本&#xff0c;这个必须写…

jmap-各种option参数说明

基本情况 jmap&#xff08;JVM Memory Map&#xff09;&#xff1a;作用一方面是获取dump文件&#xff08;堆转储快照文件&#xff0c;二进制文件&#xff09;&#xff0c;它还可以获取目标Java进程的内存相关信息&#xff0c;包括Java堆各区域的使用情况、堆中对象的统计信息…