【C++】stack和queue 适配器

                                                

🔥个人主页:北辰水墨

🔥专栏:C++学习仓

Alt

本节内容我们来讲解栈和队列的模拟实现,文末会赋上模拟实现的代码

 一、stack的使用和模拟实现

 stack适配器的介绍:

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

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

3.  stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作

  • empty:判空操作
  • back:获取尾部元素操作
  • push_back:尾部插入元素操作
  • pop_back:尾部删除元素操作

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

函数的介绍:

🔥初始化栈:

1.构建一个空栈:

std::stack<int> myst;

2.通过已有的容器,初始化栈:

std::vecotr<int> v={1,2,3,4,5};

srd::stack<int,vector<int>> myst(v); 

//stack是适配器,传相应的容器,底层就是用对应的容器实现栈

3.使用拷贝构造函数初始化一个与另一个栈相同的栈:

std::stack<int> originalStack;
// 添加一些元素到 originalStack
std::stack<int> myStack(originalStack);

 

🔥empty() 

检查栈是否为空

🔥size()

返回stack中元素的个数

🔥top()

返回栈顶元素

🔥push() 

 将元素val压栈

🔥pop()

将stack中尾部的元素弹出 

🔥swap()

交换两个栈的所有元素

// stack::swap
#include <iostream>       // std::cout
#include <stack>          // std::stackint main ()
{std::stack<int> foo,bar;foo.push (10); foo.push(20); foo.push(30);bar.push (111); bar.push(222);foo.swap(bar);std::cout << "size of foo: " << foo.size() << '\n';std::cout << "size of bar: " << bar.size() << '\n';return 0;
}

输出:

size of foo: 2

size of bar: 3

 

例题一:"最小栈"

(点击"最小栈"字体就可以跳转做题)

  1. st1 是一个标准的栈,它用于按照后进先出的顺序存储所有推入的元素
  2. st2 是一个辅助栈,它用于跟踪 s1 中所有元素的最小值
  •  MinStack() {} 不需要自己实现,走初始化列表,是自定义类型,调用他自己stack<int>的默认构造

  • void push(int x):在 st1 中推入 x。如果 st2 为空或者 x 小于等于 st2 的栈顶元素,也将 x 推入 st2这保证 st2 的栈顶元素始终是 st1 中当前所有元素的最小值

  • void pop()从 st1 中弹出一个元素。如果 st1 的栈顶元素与 st2 的栈顶元素相等,说明 st1 弹出的元素是当前的最小值,因此也需要在 st2 中弹出栈顶元素

class MinStack {
public:/** initialize your data structure here. */MinStack() {}void push(int x) {st1.push(x);if(st2.empty()||st2.top()>=x) st2.push(x);}void pop() {if(st1.top()==st2.top()){st2.pop();}st1.pop();}int top() {return st1.top();}int getMin() {return st2.top();}
private:stack<int> st1;stack<int> st2;
};

例题二:"验证栈序列"

 核心思想:模拟入栈和出栈的过程。

n: 记录poped的下标

pushst:创建一个栈

 for循环开始模拟入栈的过程,只要 i 没有大于 pushed.size() 个数就继续循环

while循环:栈不为空&&栈顶元素==poped[n]就进入循环,出栈并且让n++

class Solution {
public:bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {stack<int> pushst;int n = 0;for(int i=0;i<pushed.size();i++){pushst.push(pushed[i]);while (!pushst.empty()&&pushst.top() == popped[n]) //这个的顺序不能反,否则当为空的时候,去取栈顶元素,会报错{pushst.pop();n++;}}return pushst.empty();}
};

 

栈的模拟实现:

namespace ink
{template<class T,class Container=vector<int>>class stack{public:bool empty(){return _con.empty();}size_t size(){return _con.size();}//加引用,是为了自定义类型,减少拷贝构造const T& top()  //const 返回的是栈顶元素,用引用返回,外面的就可以接收并且可以修改,我就让它只读{return _con.back();}void pop(){_con.pop_back();}void push(T& x){_con.push_back(x);}private:Container _con;};
}

上面的实现是简单地展示了如何用C++模板和通用编程的原则来定义一个通用的栈类,这个栈类被称为适配器。在这种上下文中,“适配器模式”是一种设计模式的用词。

在容器类库设计中(如标准模板库 STL 中的容器),适配器模式通常用于通过已有的容器类型(如vectordequelist等),来实现某种特定的抽象数据类型(如栈、队列等)的接口。这样的做法使我们能够重用现有代码,并提供更丰富的操作 

  • 定义了 stack 模板类,它接收两个模板参数:
    • T: 栈中元素的类型。
    • Container: 底层容器的类型,默认是 vector<T>

Container 是一个模板参数,它允许我们定义底层数据结构。默认使用 std::vector<T> 作为底层容器,但我们可以指定 std::deque<T>std::list<T>等容器,这是适配器模式的应用之一,我们可以切换不同的底层实现,不改变栈的接口 (底层千差万别,不改变上层接口)

stack 类包含如下成员函数:

  • push: 向栈中添加元素
  • pop: 从栈中移除顶部元素
  • size: 返回栈中元素的数量
  • empty: 检查栈是否为空
  • top: 返回栈顶元素的引用

这些成员函数中的每一个都直接调用了底层容器 Container 实例 _con 的相应操作函数,这样 stack 就提供了类似栈的接口

 

二、queue的使用和模拟实现

queue适配器的介绍

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

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

3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

  • empty:检测队列是否为空
  • size:返回队列中有效元素的个数
  • front:返回队头元素的引用
  • back:返回队尾元素的引用
  • push_back:在队列尾部入队列
  • pop_front:在队列头部出队列

 4.队列需要支持pop_front的操作,而vector不支持。所以vector不能做queue的底层容器

5.一般我们都是使用deque容器作为queue的底层容器。

deque容器的介绍

 

deque 成为双端队列,是一种序列容器,在两端都支持高效的元素插入和删除操作。

与 std::vector 相比,std::deque 提供类似的功能,但在许多实现中,deque 是由多个固定大小的数组(通常被称为块或段)组成的动态数组。这允许在两端进行快速的插入和删除操作,而不必像 std::vector 在插入(或删除)元素时将所有元素向前或向后移动。

deque 的主要特点和功能包括:

1. 双端操作:可以在队列的前端和后端进行插入 (push_front, emplace_front) 和删除 (pop_front) 操作


2. 序列访问:可以使用下标操作符 (operator[]) 或一系列迭代器访问 deque 中的元素

3. 迭代器失效:在两端添加或删除元素通常不会使迭代器失效,但是在 deque 中除了首尾外的任何位置插入或删除元素都可能使所有迭代器失效。这取决于具体的实现。

4. 内存分配:deque 不保证所有元素都连续存储,因此不能依赖像 std::vector 那样的内存连续性

5. 性能:在两端插入或删除元素通常是常数时间复杂度 O(1),但是在中间位置插入或删除元素的时间复杂度通常是线性的 O(n),这取决于插入位置与最近端点的距离

vector的优点在于能支持下标随机访问,缺点是头部或中间插入删除的效率低,扩容有消耗

list的优点在于任意位置插入删除的效率都不错,缺点就是不支持下标的随机访问

而deque可以看做vector和list的中和版,既支持下标访问,又支持头插头删

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的

std::deque 的常见实现方式是使用一系列的固定大小的数组(称为缓冲区或块),这些数组被指针所管理,这些指针通常保存在一个或多个中央数组中。这种实现允许在 deque 的两端都高效地添加或删除元素,而无需移动所有元素 

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

中控数组满了就扩容,它的消耗会小很多

 

它的迭代器有四个指针

  • start指向指向第一个buff的第一个数据
  • finish指向最后一个buff的最后一个数据的下一个位置
  • cur指向buff的头节点
  • node指回中控数组(为了让迭代器在走完上一层之后可以跳转到下一块空间的首元素)

deque的缺陷

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

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

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

为什么选择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的效率高(扩容时不需要搬移大量数据);deque不需要像list那样频繁的开空间,queue/stack中的元素增长时,deque不仅效率高,而且内存使用率高

 

queue的模拟实现

#include<deque>
#include<list>
namespace own
{template<class T, class Con = deque<T>>class queue{public:queue() {}void push(const T& x) { _c.push_back(x); }void pop() { _c.pop_front(); }T& back() { return _c.back(); }const T& back()const { return _c.back(); }T& front() { return _c.front(); }const T& front()const { return _c.front(); }size_t size()const { return _c.size(); }bool empty()const { return _c.empty(); }private:Con _c;};
}

 

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

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

相关文章

基于openEuler22.03 LTS环境的容器项目实训——分布式微服务项目部署

一、说明 本文配置环境为VMware虚拟机&#xff08;4核CPU&#xff0c;4 GB内存&#xff0c;40GB磁盘&#xff09;&#xff0c;OS为openEuler 22.03 LTS &#xff0c;虚拟机要求能联网。 二、安装docker环境 2.1 安装docker相关软件包 [rootnode01 ~]# dnf -y install docker…

Windows环境下VSCode加MinGw-W64搭建C/C++开发环境

前言&#xff1a; 本文记录了自己在配置 Windows环境下 VSCode&#xff0c;并安装MinGW-W64来搭建windows操作系统下下的C/C开发环境。本文重点参考了如下链接中知乎上的文章里介绍的方法&#xff0c;在windows上安装 MinGW-W64。 vscode c/c环境配置&#xff08;MinGW&…

深度学习中的一些概念

训练术语 欠拟合 欠拟合是指模型没有很好地捕获到数据特性&#xff0c;不能完整地表示数据的全部信息&#xff0c;也就是模型的复杂度低于应有的水平。例如&#xff0c;假设一个数据集实际上服从二阶多项式分布&#xff0c;但我们使用一阶线性模型去拟合它&#xff0c;这样的…

开发时如何快速分析代码和生成测试方法(Baidu Comate插件帮我一键分析)

目录 前言 Baidu Comate智能编码助手简介 安装教程 使用RabbitMQ一个绑定队列方法进行演示 进行测试现有功能 使用感觉 测试结果 前言 因为在开发代码的时候&#xff0c;发现有很多都是废话也不是很想写注释 的&#xff0c;毕竟程序员最讨厌的两件事情&#xff0c;一…

AI 重塑产品设计

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

Chrome的常用操作总结

Chrome的常用操作总结 最近的自己真的好忙啊,好久真好久没有写博客了,今天我就趁着周末的这段时间总结一下最近自己的用的Chrome浏览器常用的命令 不得不说: 就是特么的丝滑!吊打一切浏览器(不接受反驳哈哈哈)因为反驳我也不听嘻嘻 用好快捷键,就是事半功倍!!!重要的事儿说一遍…

【定制化】在Android平台实现自定义的程序启动页

特别说明&#xff1a;以下仅适用于Android平台。 实现原理 创建安卓端自定义的Activity禁用UnityPlayerActivity的启动Logo改用自定义Activity 示例效果 参考简单步骤或详细步骤都可实现。 自定义的启动动画&#xff0c;效果如下&#xff1a; 简单步骤 三步操作实现启动动画…

Vue3项目Easy云盘(二):文件列表+新建目录+文件重命名+文件上传

一、文件列表 1.封装全局组件Table.vue 因为Main.vue等都会用到文件列表table&#xff0c;所以直接封装成组件。 src/components/Table.vue <template><!-- 表格 --><div><el-tableref"dataTable":data"dataSource.list || []":h…

用迭代加深解决加成序列问题

可以看到这个最坏的结果是100层搜索&#xff0c;但是其实1 2 4 8 16 32 64 128&#xff0c;到128的话也只要8&#xff0c;所以大概只需要10几层搜索就可以解决了&#xff0c;这个时候就可以用迭代加深的方法&#xff0c;深度一点点的加&#xff0c;如果大于概深度就舍去。有人说…

用户登录后端:登录密码解密后用PasswordEncoder验证密码是否正确

前置知识: 前端登录加密看用户登录 PasswordEncoder加密看PasswordEncoder详解 项目中因为要判断用户登录密码是否正确&#xff0c;通过输入错误次数锁住用户 1.后端配置rsa私钥 #密码加密传输&#xff0c;前端公钥加密&#xff0c;后端私钥解密 rsa:private_key: xxxx2. 读…

基于C++基础知识的指针

一、变量与指针 在C中&#xff0c;变量是用来存储数据的一个标识符&#xff0c;而指针是一个变量&#xff0c;该变量存储的是另一个变量的地址。 变量可以是不同的数据类型&#xff0c;包括整数&#xff08;int&#xff09;、浮点数&#xff08;float&#xff09;、字符&#…

智慧粮库/粮仓视频监管系统:AI视频智能监测保障储粮安全

智慧粮库视频监管系统是一种基于物联网、AI技术和视频监控技术的先进管理系统&#xff0c;主要用于对粮食储存环境进行实时监测、数据分析和预警。TSINGSEE青犀智慧粮库/粮仓视频智能管理系统方案通过部署多区域温、湿度、空气成分等多类传感器以及视频监控等设施&#xff0c;对…

IDEA及Maven配置代理及Maven中央仓库配置详解

一、配置代理 首先&#xff0c;需要本地开启代理入口&#xff0c;如图。 这个跟你使用代理软件有关。像我使用的是qv2ray。 其次&#xff0c;idea配置代理&#xff0c;如图。 1.1 idea配置代理 打开Settings&#xff0c;如图 1.2 maven配置代理 maven配置代理&#xff0c;修…

虚拟机CentOS密码重置

1&#xff0c;reboot重启 在出现下面的界面1按e 如果有选项就选择“CentOS Linux &#xff08;3.10.0-327.e17.x86_64&#xff09;7 &#xff08;Core&#xff09;”【我的电脑没有直接显示界面2】 界面1 界面2 2&#xff0c;在上述界面2中继续按e进入编辑模式 找到“ro cr…

partially initialized module ‘replicate‘ has no attribute ‘run‘

partially initialized module replicate has no attribute run(most likely due to a circular import) 在包名上停留查看impot 包的地址。 报错原因&#xff1a; 文件重名了&#xff0c;导入了 当前文件 。 修改文件名 即可。

Vue3路由及登录注销功能、设置导航守护功能模块

路由 在vue中&#xff0c;页面和组件都是.vue文件&#xff0c;可以说是一样的&#xff0c;结构、内容和生产方法都是一样&#xff0c;但是组件可以被反复使用&#xff0c;但页面一般只被使用一次。 路由的作用就是网页地址发生变化时&#xff0c;在App.vue页面的指定位置可以加…

17 M-LAG 配置思路

16 华三数据中心最流行的技术 M-LAG-CSDN博客 M-LAG 配置思路 什么是M-LAG&#xff1f;为什么需要M-LAG&#xff1f; - 华为 (huawei.com) 1 配置 M-LAG 的固定的MAC地址 [SW-MLAG]m-lag system-mac 2-2-2 2 配置M-LAG 的系统标识符系统范围1到2 [SW-MLAG]m-lag system-nu…

MongoDB安装及接入springboot

环境&#xff1a;windows、jdk8、springboot2 1.MongoDB概述 MongoDB是一个开源、高性能、无模式&#xff08;模式自由&#xff09;的文档&#xff08;Bson&#xff09;型数据库&#xff1b;其特点如下&#xff1a; 模式自由 ---- 不需要提前创建表 直接放数据就可以 支持高并…

STM32窗口看门狗的操作

STM32的窗口看门狗的主要功能是&#xff0c;程序过早的喂狗还有太晚喂狗&#xff0c;都会触发单片机重启&#xff0c;就是有一个时间段&#xff0c;在这个时间段内喂狗才不会触发单片机重启。 下面我就总结一下窗口看门狗的设置过程&#xff1a; 第一步&#xff1a;开启窗口看…

vscode怎么设置背景图片?

vscode背景图片是可以自己设置的&#xff0c;软件安装后默认背景的颜色是黑色的&#xff0c;这是默认的设计&#xff0c;如果要修改背景为指定的图片&#xff0c;那么我们需要安装插件&#xff0c;然后再通过代码来设置背景图片的样式&#xff0c;下面我们就来看看详细的教程。…