【c++】深入剖析与动手实践:C++中Stack与Queue的艺术

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章我们来到STL新的内容,stack和queue

目录

  • 1. stack的介绍与使用
    • `函数介绍`
    • `例题一:最小栈`
    • `例题二:栈的压入、弹出队列`
    • `栈的模拟实现`
  • 2.queue的介绍和使用
    • `deque的介绍`
      • `deque的缺陷`
    • `queue的模拟实现`

1. stack的介绍与使用

在这里插入图片描述

  1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
  2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出
  3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
    • empty:判空操作
    • back:获取尾部元素操作
    • push_back:尾部插入元素操作
    • pop_back:尾部删除元素操作
  4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque

在这里插入图片描述

函数介绍

🔥构造函数

在这里插入图片描述

explicit stack (const container_type& ctnr = container_type());

这个构造函数定义的是 std::stack 类模板的一个构造函数,它接受一个参数,类型是 container_type。这里的 container_typestd::stack 的成员类型,它表示用于内部存储的容器类型,通常是某种顺序容器比如 std::dequestd::liststd::vector

关键字 explicit 表示这个构造函数禁止隐式类型转换。换句话说,你不能隐式地从 container_type 赋值给 std::stack 对象,而必须显式地调用构造函数:

std::deque<int> mydeque(3,100);          // deque with 3 elements
std::stack<int> first (mydeque);          // stack initialized to copy of deque

上面的代码中,我们创建了一个 std::deque<int> 对象 mydeque,然后使用它显式地构造一个 std::stack<int> 对象 first。如果没有 explicit 关键字,下面的代码也是有效的:

std::stack<int> myStack = mydeque; // 这一行在 explicit 关键字存在时是不合法的

但有 explicit 关键字时,这种隐式转换就会产生编译错误。

构造函数的参数 ctnr 还有一个默认值 container_type()。这表示如果在构造 std::stack 对象时没有提供参数,将会使用 container_type 的默认构造函数创建一个新的空容器作为 std::stack 的内部存储。这允许你像下面这样简单地创建一个空栈:

std::stack<int> myStack; // 空栈,使用默认的底层容器(通常是 std::deque)

在这种情况下,myStack 是空的,因为没有向构造函数传递任何参数,它会使用底层容器类型的默认构造函数创建一个空的内部容器

🔥empty()

在这里插入图片描述

检测stack是否为空

🔥size()

返回stack中元素的个数

🔥top()

在这里插入图片描述

返回栈顶元素的引用

🔥push()

在这里插入图片描述

将元素val压入stack中

🔥pop()

在这里插入图片描述

将stack中尾部的元素弹出

例题一:最小栈

题目链接:155.最小栈
题目描述在这里插入图片描述

为了实现上面这个栈,我们需要使用两个栈

stack<int> s1;
stack<int> s2;
  1. s1 是一个标准的栈,它用于按照后进先出的顺序存储所有推入的元素
  2. s2 是一个辅助栈,它用于跟踪 s1 中所有元素的最小值
  • MinStack():构造函数,初始化两个空栈 s1s2

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

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

  • int top()返回 s1 的栈顶元素,即 MinStack 的栈顶元素

  • int getMin()返回 s2 的栈顶元素,即 s1 中当前所有元素的最小值

代码实现如下:

class MinStack {
public:
    MinStack() {
    } 
    void push(int val) {
        s1.push(val);
        if(s2.empty()||s2.top()>=val)
        {
            s2.push(val);
        }
    }
    void pop() {
        if(s1.top()==s2.top())
        {
            s2.pop();
        }
        s1.pop();
    }
    
    int top() {
        return s1.top();
    }
    
    int getMin() {
       return s2.top();
    }
private:
    stack<int> s1;
    stack<int> s2;
};

例题二:栈的压入、弹出队列

题目链接:牛客
题目描述在这里插入图片描述

该函数的目的是检查给定的出栈顺序 popV 是否能由相应的入栈顺序 pushV 实现。换句话说,函数判断是否存在某种方式,使得按 pushV 指定的顺序入栈后,能够按 popV 指定的顺序出栈

代码实现如下:

class Solution {
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        int pushi=0,popi=0;
        stack<int> s;
        while(pushi<pushV.size())
        {
            s.push(pushV[pushi]);
            while(!s.empty()&&s.top()==popV[popi])
            {
                s.pop();
                popi++;
            }
            pushi++;
        }
        return s.empty();
    }
};

函数的实现逻辑如下:

  1. 初始化两个整型指针 pushipopi 分别为 0,表示入栈和出栈序列的开始索引

  2. 创建一个辅助的栈 s 用于模拟入栈和出栈的过程

  3. 使用一个 while 循环开始模拟入栈的过程,只要 pushi 没有指向 pushV 结尾就继续循环

  4. 在每次循环中,将 pushV 中当前位置 pushi 的元素推入栈 s

  5. 然后,使用一个内部 while 循环检查此时栈顶元素是否等于 popV 中相应位置 popi 的元素:

    • 如果相等,则从栈 s 中弹出栈顶元素,并将 popi 指针后移一位以检查下一个出栈元素
    • 如果不相等或栈已空,则中断内部 while 循环
  6. 在外部 while 循环结束一次循环之后,将 pushi 指针后移一位继续下一轮入栈操作

  7. 最后,当外部 while 循环结束时,检查栈 s 是否为空:

    • 如果栈为空,表示所有入栈的元素都能按 popV 指定的顺序出栈,返回 true
    • 如果栈不为空,表示存在无法按给定出栈顺序出栈的元素,返回 false

栈的模拟实现

namespace own
{	
	// 设计模式
	// 适配器模式 -- 转换
	// stack<int, vector<int>> st1;
	// stack<int, list<int>> st2;
	template<class T, class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}

		const T& top()
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}

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

在面向对象的设计模式中,适配器模式(Adapter Pattern)通常用来将一个类的接口转换成客户期望的另一个接口。适配器让那些由于接口不兼容而不能一起工作的类可以一起工作

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

在上面的代码段中:

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

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

  • stack 类包含如下成员函数:

    • push: 向栈中添加元素
    • pop: 从栈中移除顶部元素
    • size: 返回栈中元素的数量
    • empty: 检查栈是否为空
    • top: 返回栈顶元素的引用
  • 这些成员函数中的每一个都直接调用了底层容器 Container 实例 _con 的相应操作函数,这样 stack 就提供了类似栈的接口

这个适配器堆栈类可以看作是对底层容器的一个封装,它只暴露了限定的一组操作(栈操作),提供了符合 LIFO(后进先出)原则的栈

总结来说,这个 stack 类是一个栈适配器,它利用模板为不同的底层容器提供了统一的栈接口。可以选择使用 vectordequelist等容器作为存储机制,并且无需修改外部代码

2.queue的介绍和使用

在这里插入图片描述

  1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素
  2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
  3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
    • empty:检测队列是否为空
    • size:返回队列中有效元素的个数
    • front:返回队头元素的引用
    • back:返回队尾元素的引用
    • push_back:在队列尾部入队列
    • pop_front:在队列头部出队列
  4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque
函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

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并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组

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

在这里插入图片描述

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

在这里插入图片描述
中控数组满了就扩容,它的消耗会小很多

在这里插入图片描述
它的迭代器有四个指针

  • start指向指向第一个buff的第一个数据
  • finish指向最后一个buff的最后一个数据的下一个位置

deque的缺陷

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

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

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

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

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

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作
  2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高

结合了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;
    };
}

本节内容到此结束!!感谢大家阅读

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker 的数据管理 与 Docker 镜像的创建

目录 一、Docker 的数据管理 1.1.数据卷 1.2.数据卷容器 1.3.容器互联&#xff08;使用centos镜像&#xff09; 二、Docker 镜像的创建 2.1.基于现有镜像创建 2.2.基于本地模板创建 2.3.基于Dockerfile创建 2.3.1联合文件系统&#xff08;UnionFs&#xff09; 2.3.2…

GDPU 竞赛技能实践 天码行空9

1. 埃式筛法 求区间[2, n]内所有的素数对 &#x1f496; Main.java import java.util.Scanner;public class Main {static int N (int) 1e8, cnt 0;static int[] p new int[N];static boolean[] st new boolean[N];public static void main(String[] args){Scanner sc …

使用grasshopper修改梁的起始点方向

一般北方向朝上的情况&#xff0c;梁的方向从南向北&#xff0c;从西向东。 现在使用grasshopper来判断起始点坐标&#xff0c;分辨是否错误。 交换起始点这个&#xff0c;我实在不会用电池操作&#xff0c;只好敲python代码实现了。代码如下&#xff1a; 如果会敲代码的同学…

66.网络游戏逆向分析与漏洞攻防-利用数据包构建角色信息-重新规划游戏分析信息的输出

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

Apache Seata的可观测实践

title: Seata的可观测实践 keywords: [Seata、分布式事务、数据一致性、微服务、可观测] description: 本文介绍Seata在可观测领域的探索和实践 author: 刘戎-Seata 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Seata简介 Seata的…

STM32单片机通过ST-Link 烧录和调试

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 1. ST-LINK V2 ST LINK v2下载器用于STM32单片机&#xff0c;可以下载程序、调试程序、读取芯片数据&#xff0c;解除芯片读写保护等等&#xff0c;辅助软件用的是STM32 ST-LINK Utility。 STM32 ST-LINK Utility…

电脑上的任务管理器不见了?如何把它打开?

前言 今天小白在睡觉的时候突然梦见回到了学校的电脑教室…… 相信大家都会有体验&#xff1a;每次上电脑课的时候&#xff0c;老师都会通过某些软件监控和控制学生的电脑。 想退出被控端的软件&#xff1f;没机会&#xff01;毕竟任务管理器也被禁用了&#xff0c;想整活都…

算法学习之单调栈

发射站 题目描述 某地有 N N N 个能量发射站排成一行&#xff0c;每个发射站 i i i 都有不相同的高度 H i H_i Hi​&#xff0c;并能向两边&#xff08;两端的发射站只能向一边&#xff09;同时发射能量值为 V i V_i Vi​ 的能量&#xff0c;发出的能量只被两边最近的且比…

Opencv_14_多边形填充与绘制

绘制多边形&#xff1a; 1&#xff09;coInvert.polyline_drawing(src); 2&#xff09;void ColorInvert::polyline_drawing(Mat& image) { Mat canvas Mat::zeros(Size(512, 512), CV_8UC3); Point p1(100, 100); Point p2(150, 100); Point p3(200…

TR6 - Transformer实战 单词预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目录 理论知识关于数据集 Wikitext-2 模型结构代码实现0. 环境1. 加载数据集2. 模型搭建3. 创建模型4. 单轮训练和评估的流程5. 训练 模型效果总结与心得体会 …

Openharmony - 设备异常关机Power Down问题分析

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 1.问题描述1.1出现power down的原因1.1.1硬件故障或信号1.1.2软件错误或系统崩溃2.抓日志信息2.1.抓日志方法2.2.问题初步分析3.问题排…

React复习笔记

基础语法 创建项目 借助脚手架&#xff0c;新建一个React项目(可以使用vite或者cra&#xff0c;这里使用cra) npx create-react-app 项目名 create-react-app是React脚手架的名称 启动项目 npm start 或者 yarn start src是源文件index.js相当于Vue的main.js文件。整个…

OC类与对象

OC类与对象 本篇是对上一篇的内容的继续学习。从单例模式开始继续学习 文章目录 单例模式定义应用场景特点单例模式的创建 隐藏与封装理解什么是封装目的访问控制符合成存取方法特性的指示符点语法访问属性 对象初始化便利的初始化方法 类的继承特点语法格式重写父类方法super关…

SimpleDateFormat类.Java

目录 1.1构造方法 1.2格式规则 1.3常用方法 1.4练习1&#xff08; 出生日期&#xff09; 1.5练习2(秒杀活动) java.text.SimpleDateFormat 是日期/时间格式化类&#xff0c;我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来…

机器学习理论基础—集成学习(1)

机器学习理论基础—集成学习 个体与集成 集成学习通过构建并结合多个学习器来完成学习任务&#xff0c;有时也称为多分类系统等。 分类&#xff1a; 根据集成学习中的个体学习器的不同可以分为同质集成&#xff08;集成的学习器相同例如全部是决策树&#xff09;&#xff0c…

上市公司专利数据、专利申请、专利授权和质量指标计算面板数据(1990-2022年)

01、数据简介 专利作为企业创新能力和核心竞争力的体现&#xff0c;越来越受到上市公司的重视。了解上市公司的专利数据、专利申请、专利授权和质量指标计算&#xff0c;有助于投资者更好地评估公司的创新能力和长期发展潜力。 通过分析上市公司的专利数据、专利申请、专利授…

【国标语音对讲】EasyCVR视频汇聚平台海康/大华/宇视摄像头GB28181语音对讲配置

一、背景分析 近年来&#xff0c;国内视频监控应用发展迅猛&#xff0c;系统接入规模不断扩大&#xff0c;涌现了大量平台提供商&#xff0c;平台提供商的接入协议各不相同&#xff0c;终端制造商需要给每款终端维护提供各种不同平台的软件版本&#xff0c;造成了极大的资源浪…

[C++ QT项目实战]----系统实现双击表格某一行,表格数据不再更新,可以查看该行所有信息,选中表更新之后,数据可以继续更新

前言 在需要庞大的数据量的系统中&#xff0c;基于合适的功能对数据进行观察和使用至关重要&#xff0c;本篇在自己项目实战的基础上&#xff0c;基于C QT编程语言&#xff0c;对其中一个数据功能进行分析和代码实现&#xff0c;希望可以有所帮助。一些特殊原因&#xff0c;图片…

回溯-单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相…

压测--混合场景设置

1、设计测试场景 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标满足需求定义的检验活动。一般有以下场景&#xff1a; 基准场景&#xff1a;单接口少量并发用户下压测&#xff0c;评估单个功能点性能。负载场景&#xff1a;逐步增…