stack与queue的简单封装

    前言: stack与queue即栈和队列,先进后出/先进先出的特性我们早已了然于心,  在学习数据结构时,我们利用c语言实现栈与队列,从结构体写起,利用数组或指针表示他们的数据成员,之后再一个个实现他们的函数接口,简单来说我们是直接手撕栈和队列的。

但对于c++来说,c++提供了模板,提供了标准模板库,利用这些们只需要实现它的特性,然后再封装它即可。

在实现之前,我们再来了解一下何为适配器?

适配器

本质上是一种设计模式-适配器模式:

Adapter(适配器模式)属于结构型模式,结构性模式关注的是如何组合类与对象,以获得更大的结构,我们平常工作大部分时间都在与这种设计模式打交道。

意图:将一个类的接口转换成客户希望的另一个接口。Adapter 模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。
对于统一多个类的接口适配器模式就比较适用。

适配器, 在STL中扮演着转换器的角色,用于将一种接口转换成另一种接口,从而是原本不兼容的接口能够很好地一起运作。

容器、迭代器、和函数都有适配器,  适配器不提供迭代器。

栈的简单封装

一般对于我们自己来说实现一个栈,利用c++提供的STL我们可以直接封装,比如我们直接利用vector表示整个栈,出栈即尾删,入栈即尾插,获取栈顶元素即返回数组末尾元素,其他接口我们也不难实现:

template <class T>class stack      
{
public://栈的重要接口//入栈viod push(cosnt T& x){_container.push_back(x);//直接调用尾插}//出栈void pop(){_container.pop_back(x);//直接调用尾删}//获取栈顶元素const T& top(){return _container.back();//返回尾元素}//大小size_t size(){return _container.size();}//判空bool empty(){return _container.empty();}private://直接利用容器vector<T>> _container;};

 添加容器模板参数(适配器)

    对于栈我们知道本质是数组,那么直接调用数组模板即可,但对于栈的实现我们发现不仅仅直接利用数组,利用链表也是可以实现栈的,其次由于在设计标准模板库时根据泛型编程的思维,这使得接口其功能都是一样的()。

那么在实现栈的时候,不仅仅利用一种模板,而是多模板,这使得我们在设计实现栈时,可以提供一个容器模板(根据不同的容器也能实现栈),因此我们还添加了Container(容器模板),达到实现不同容器,统一接口的实现。

template <class T,class Container>class stack      
{
public://栈的重要接口//入栈void push(const T& x){_container.push_back(x);//直接调用尾插}//出栈void pop(){_container.pop_back();//直接调用尾删}//获取栈顶元素const T& top(){return _container.back();//返回尾元素}//大小size_t size(){return _container.size();}//判空bool empty(){return _container.empty();}const T& front(){return _container.front();}const T& back(){return _container.back();}
private://直接利用容器Container _container;};

  对于这里的容器模板,其实就是适配器,通过参数控制不同容器适配出同样效果的数据结构(感觉这也是泛型编程的特点,不追求底层,只看当前的功能)。

队列的简单封装

通过对栈的是实现,我们也可以利用适配器这种方式来实现队列,不过需要注意的是:

我们在选用容器的时候还是要应当注意容器是否满足我们在实现某个功能时,提供所需要的接口,对于队列,这里使用vector就不行了,并不能一股脑认为是个容器都可以。

template<class T, class Container = deque<T>>class queue
{
public://入队void push(const T& x){_con.push_back(x);}//出队void pop(){_con.pop_front();}void top(){return _con.front();}size_t size(){return _con.size();}const T& front(){return _con.front();}const T& back(){return _con.back();}bool empty(){return _con.empty();}
private:Container _con;};

库中的实现


  利用适配器我们可以将能实现的容器集合在一起,控制参数来控制容器,那么既然是参数,我们想这里也应该可以设置缺省参数的,那么缺省参数如何设置呢?我们可以看看库中的实现:

初识双端队列

库中这里设置的缺省参数是双端队列,我们可以看看双端队列:

 首先对于双端队列,我们不要认为他是一个队列,其次我们在观察它的接口:

双端队列,本质上是一个顺序表,可以看到它的接口与vector和list非常相似,从功能上来说,他是将vector与list的功能集合到一起,同时具有两种结构的接口(包括尾插,尾删,头插,头删,正反向迭代器,[ ]访问元素,插入,删除等接口),可以看到非常之牛,它好像同时满足了vector和list的所有优点:

1.vector的连续地址,支持快速访问。

2.list的高效率随机位置插入。

犹如六边形战士,那么真实情况果真如此吗?事实并非如此。

我们可以了解一下他的结构,因为能支持数组随机访问,且能随机插入,那么其空间应该是连续的,但不完全连续:

即利用指针,将各个数组连接起来,指针被存放在叫中控数组的地方,存放数据的叫做buffer数组。

迭代器设计:

 可以看到迭代器是由四个数据封装起来的,其中第一个位置的cur指向的是当前数组的第一个位置,第二个位置的first指向的是数组第一个位置,第三个last指向的是数组最后位置,二级指针node指向的是中控数组里的某个指针。

以这样的方式封装了begin与end这两个表示开头语结尾的迭代器。

        对于双端队列,它的数据插入是从中间开始的,如若头或尾某个地方的数据已经满了,那么他会新开一个buffer数组,将数组存放起来(头部插入,放数组后面,尾部插入放数组前面),再连接起来,当然如果数据够多使得中控数组都已经插入满了,那么需要扩容中控数组,不过代价会比较低,但是对于中间插入数据,如若中间的某个位置的buffer数组已经满了,那么有两种选择方案:

扩容或者挪动数据,如何选择看你的取舍:

若果是扩容的话,因为我们[ ]访问数据对于同样大的数组访问速度会比较快,我们可以很好的计算出它的位置从而访问它(比如我们对数组的大小取余或取整,就可以知道是某个数组的第几个),若扩容,则该位置访问是比较困难的。

如果是挪动数据的话,整体后挪数据,数据量太大,那么代价就非常大了,效率会特别低。

这样一看其实是对于双端队列,我们头插尾插数据时效率是比较高的,但对于中间插入数据实在是不太行啊,这也是为什么叫双端队列,目的也是让我们去操作它的两端数据,不去中间搞事。

那么回过头来看选用双端队列用作我们的栈和队列的适配器,其实是比较不错的,只操作两端的数据,效率还是比较高的。

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

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

相关文章

Mycat与ShardingSphere如何选择(未完待续)

区别 MycatSharding-JDBCSharding-ProxySharding-Sidecar官方网站官方网站官方网站官方网站官方网站源码地址GitHubGitHubGitHubGitHub官方文档Mycat 权威指南官方文档官方文档官方文档开发语言JavaJavaJavaJava开源协议GPL-2.0/GPL-3.0Apache-2.0Apache-2.0Apache-2.0数据库M…

漏刻有时数据可视化Echarts组件开发(30):自定义方向的数据流向动画

let points = [{name: 市城市管理局,desc: {type: 1,},},{name: 科大讯飞,desc: {type: 2,},},{name: 市水利局,desc: {type: 1,},},{name: 市政法委,desc: {type: 1,},},{name: 市自然资源和规划局,desc: {type: 1,},},{name: 市生态环境局,desc: {type: 1,},},{name: 市大数据…

Linux:centos9的本地yum仓库配置

其实9和7的配置方法是差不多一样的&#xff0c;只不过你使用7的本地yum仓库里面直接挂载就可以直接把仓库位置指向挂载点 具体可以看我往期文章&#xff0c;但是先看完我下面的描述再去看我链接的文章才能看懂如何配置centos9的yum仓库 Linux&#xff1a;YUM仓库服务_鲍海超-…

监督学习和非监督学习, 半监督学习和增强学习

监督学习 给机器的训练数据拥有“标记”或者“答案”&#xff0c; 也就是又有x、又有y 监督的意思是说我们针对给机器的数据进行了正确答案的划分&#xff0c; 这个正确的答案的本身就被称为监督的信息 比如&#xff1a; 生活中很多学习的过程都是监督学习的过程&#xff0c;…

pom.xml中解决“vulnerable dependency maven:org.yaml:snakeyaml:1.33“警告问题

问题 当我们引入依赖的时候&#xff0c;pom文件会有这样的提示&#xff0c;其大概的意思就是 maven:org.yaml:snakeyaml:1.30"表示通过Maven引入了一个潜在的安全漏洞依赖项"org.yaml:snakeyaml:1.30" 解决办法 其实我们就是要更改这个依赖的版本&#xff0c…

MongoDB(一) windows 和 linux 之 Ubuntu 安装

数据库分类 一、关系型数据库&#xff08;RDBMS&#xff09; mysql 、Oracle、DB2、SQL Server 关系数据库中全都是表 二、非关系型数据库&#xff08;NO SQL&#xff09; MongoDB、Redis 键值对数据库 文档数据库MongoDB 下载 mongoDB https://www.mongodb.com/try/downloa…

你写过的最蠢的代码是?

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

9.22数电(触发器寄存器一些电路分析reg的思考)

用作存储元件的电路 新输入的信号可能使电路保持同样的状态也可能使电路进入另一种新的状态 输入信号置位与复位可以用于改变构成存储元件的电路状态 RS锁存器 通过或非门就是说输入信号中有一个是1&#xff0c;输出就的是0 在RS0时&#xff0c;RS对或非门的结果无影响&am…

蓝桥杯 题库 简单 每日十题 day7

01 啤酒和饮料 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。啤酒每罐2.3元&#xff0c;饮料每罐1.9元。小明买了若干啤酒和饮料&#xff0c;一共花了82.3元。我们还知道他买的啤酒比饮料的数量少&#xff0c;请你…

蓝牙电话之HFP—电话音频

1 媒体音频&#xff1a; 播放蓝牙音乐的数据&#xff0c;这种音频对质量要求高&#xff0c;数据发送有重传机制&#xff0c;从而以l2cap的数据形式走ACL链路。编码方式有&#xff1a;SBC、AAC、APTX、APTX_HD、LDAC这五种编码方式&#xff0c;最基础的编码方式是SBC&#xff0…

07_ElasticSearch:倒排序索引与分词Analysis

07_ElasticSearch&#xff1a;倒排序索引与分词Analysis 一、 倒排索引是什么&#xff1f;1.1 通过示例&#xff0c;简单理解下1.2 核心组成 二、倒排索引是怎么工作的&#xff1f;2.1 创建倒排索引2.2 倒排索引搜索 三、Analysis 进行分词3.1 Analyzer 由三部分组成3.2 Analyz…

大众滑雪赛事活动等级划分与评定规范 学习

声明 本文是学习GB-T 42374-2023 大众滑雪赛事活动等级划分与评定规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件确立了大众滑雪赛事活动的等级划分规则&#xff0c;规定了总体要求、评定主体、申报条件、等级评定 要求、结果应…