<C++> stack queue模拟实现

目录

前言

一、stack的使用

1. 接口说明

2. 例题

二、模拟实现stack

三、queue的使用

四、模拟实现queue

五、deque

总结


前言

LIFO stack

1. 栈是一种容器适配器,专门设计用于在后进先出上下文(后进先出)中运行,其中元素仅从容器的一端插入和提取。

2. stack 作为容器适配器实现,容器适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“背面”推出/弹出,这被称为堆栈的顶部

3. stack基础容器可以是任何标准容器类模板,也可以是一些其他专门设计的容器类。容器应支持以下操作:

  • empty
  • size
  • back
  • push_back
  • pop_back


4. 标准容器类,并满足这些要求。默认情况下,如果未为特定类实例化指定容器类,则使用标准容器。vectordequeliststackdeque

 

        包括queue,它们都是适配器,它们没有使用数组或链表单独实现它们的功能,它们是依靠封装的容器,将数据存在封装的容器内,然后根据要求适配出相应的需求。

        作为容器适配器的stack,它没有迭代器,因为stack的特性,不能支持迭代器的随便访问

        适配器的本质是一种复用,是一种设计模式


一、stack的使用

1. 接口说明

函数说明接口说明
stack()构造空的栈
empty()检查stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出
  • 相应的接口十分易懂,我们简单看例子即可 
	std::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);

	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}

2. 例题

例1:155. 最小栈

  • 如果只是增加一个min成员变量是有bug的,当最小值出栈后,min不会更新
  • 创建两个栈,栈1正常存数据,栈2存最小值,当栈1数据出栈时,判断其与栈2栈顶数据是否相等,若相等,则栈1、2都出栈一次;当栈1数据压栈时,判断其与栈2的栈顶数据是否相等,若相等则栈2也压栈相同数据,这是为了防止栈1有多个相同的最小值,栈1出栈最小数据后,栈2对应的最小值没有了,这就会出现问题

 

//用两个栈,一个栈存正常的数据,另一个栈存最小值
class MinStack {
public:
    MinStack() //初始化列表,默认调用自定义类型的构造函数
    {}
    
    void push(int val) {
        _st.push(val);
        if (min_st.empty() || val <= min_st.top())
        {
            min_st.push(val);
        }
    }
    
    void pop() {

        if (_st.top() == min_st.top())
        {
            min_st.pop();
        }

        _st.pop();
    }
    
    int top() {
        return _st.top();
    }
    
    int getMin() {
        return min_st.top();
    }

private:
    stack<int> _st;
    stack<int> min_st;
};

例2.  JZ31 栈的压入、弹出序列

  •  模拟出栈,每次匹配,不同,则入栈,相同则出栈
class Solution {
public:

    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        stack<int> st;
        int pushi = 0, popi = 0;    //pushi控制pushV下标,popi控制popV下标
        while (pushi < pushV.size())
        {
            st.push(pushV[pushi++]);
            if (st.top() != popV[popi])
            {
                continue;
            }
            else 
            {
                //相等则出栈
                while (!st.empty() && st.top() == popV[popi])
                {
                    st.pop();
                    ++popi;
                }
            }
        }

        return st.empty();
    }
};

         逻辑优化

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

        return st.empty();
    }
};

例3. 150. 逆波兰表达式求值 

  • 创建一个栈,遍历vector
  • 如果是数字就压栈,使用 stoi 接口
  • 如果是操作符就出栈两次,先出栈的为右操作数,后出栈的为左操作数,计算后再将结果压栈
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for (auto& str : tokens)
        {
            if (str == "+" || str == "-" || str == "*" || str == "/")
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch(str[0])
                {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            }
            else
                st.push(stoi(str));
        }

        return st.top();
    }
};

补充:如何将中序表达式改为后序表达式?

  • 开辟两个栈,遍历表达式
  • 对于操作数直接入栈1
  • 对于操作符,若栈2不为空或当前操作符比栈顶的优先级高,继续入栈;若栈不为空且当前操作符比栈顶的优先级低或相等,则将栈顶操作符压栈栈1
  • 表达式结束后,依次出栈2里面的操作符

类比:

类比stack容器,我们来看看queue例题 

102. 二叉树的层序遍历

思路:

我们可以用一种巧妙的方法修改广度优先搜索:

        1. 首先根元素入队
        2. 当队列不为空的时候
                2.1 求当前队列的长度 i

                2.2 依次从队列中取 i 个元素进行拓展,然后进入下一次迭代
        它和普通广度优先搜索的区别在于,普通广度优先搜索每次只取一个元素拓展,而这里每次取 i 个元素。在上述过程中的第 i 次迭代就得到了二叉树的第 i 层的i个元素。


class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        vector<vector<int>> ret;    //存结果
        queue<TreeNode*> q;        //存各节点
        if (!root)
            return ret;
        //入队根节点
        q.push(root);

        while(!q.empty())
        {
            //当前队列存在的数据个数就是该层的数据的个数
            int currentLeveSize = q.size();
            ret.push_back(vector<int> ());
            //当前层的所有结点的所有孩子结点,就是下一层的数据个数
            for (int i = 0; i < currentLeveSize; i ++ )
            {
                auto node = q.front();
                q.pop();
                ret.back().push_back(node->val);
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        
       return ret;
    }
};

二、模拟实现stack

        .h文件不编译,只展开,如果在

#include "Stack.h"

 之前,写了各种头文件,并且展开了命名空间std,那么程序是可以运行的,.h文件里需要使用的函数都已经在上面展开

        如果将

#include "Stack.h"
using namespace std;

        .h文件写在std之前,那么.h文件里如果使用std里的内容,编译器会报错,这是因为,编译器只会向上查找,为std是在.h文件下面展开的,所以.h文件是找不到std围起的内容

        所以,.h文件和std展开的位置是很重要的。

        我们知道,stack是适配器模式,我们之前写的数据结构只能选择顺序存储或链式存储,并不能相互转换,而适配器模式可以使用模板来随时转换储存结构,这就是大佬实现STL的stack的高处所在。

stack<int, vector<int>> st1;
stack<int, list<int>> st2;

            容器适配器:对容器进行封装,适配出我们想要的 

        对于适配器的栈,不需要构造函数、析构函数,因为它的成员变量是一个自定义类型的容器,自动调用其构造函数、析构函数.

        Container使用缺省模板

#pragma once
#include<iostream>
#include<deque>
using namespace std;

namespace my_stack
{
	//容器适配器,对容器进行封装,适配出我们想要的
	template<class T, class Container = deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

		T& top()
		{
			//不能用[],因为可能是链表
			return _con.back();
		}

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

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

	private:
		Container _con;
	};
}

三、queue的使用

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

 

函数说明接口说明
queue()构造空的队列
empty()检测队列是否为空
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

使用方法同stack 

 四、模拟实现queue

        对于queue来说,STL实现时,就按照了链表容器来实现,不能使用vector容器,因为在pop时,queue调用的是pop_front()函数,而vector没有该函数

        为什么不适配vector呢?

        问题也显而易见,vector的pop_front效率太低了

 

#pragma once
#include<iostream>
#include<deque>
using namespace std;

namespace my_queue
{
	//容器适配器,对容器进行封装
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

		T& front()
		{
			//不能用[],因为可能是链表
			return _con.front();
		}

		T& back()
		{
			//不能用[],因为可能是链表
			return _con.back();
		}

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

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

	private:
		Container _con;
	};
}

五、deque

双端队列

Deque(通常发音为“deck”)是 double-ended queue 的不规则首字母缩写。双端队列是具有动态大小的序列容器,可以在两端(其前端或后端)扩展或收缩。

特定的库可以以不同的方式实现 deque,通常作为某种形式的动态数组。但无论如何,它们都允许通过随机访问迭代器直接访问单个元素,并根据需要通过扩展和收缩容器来自动处理存储。

因此,它们提供了类似于
vector的功能,但在序列的开头,而不仅仅是在序列的末尾,都可以有效地插入和删除元素。但是,与vector不同的是,deques 不能保证将其所有元素存储在连续的存储位置:通过偏移指向另一个元素的指针来访问 a 中的元素会导致未定义的行为

vector 和 deque 都提供了非常相似的接口,可以用于类似的目的,但在内部,它们的工作方式完全不同:虽然vector使用单个数组,需要偶尔重新分配以进行增长,但 deque 的元素可以分散在不同的存储块中,容器在内部保留必要的信息,以提供对其任何元素的直接访问,时间恒定且均匀顺序接口(通过迭代器)。因此,deques 在内部比 vector 复杂一些,但这允许它们在某些情况下更有效地增长,尤其是在非常长的序列中,重新分配变得更加昂贵。

对于涉及在开头或结尾以外的位置频繁插入或删除元素的操作,deques 的性能较差,并且迭代器和引用的一致性低于列表和转发列表。

 

         可以看出,双端队列deque表现十分出色,堪称“六边形战士”,但是如此出色的容器,为什么出名度不及vector、string呢?这是因为deque对于涉及在开头或结尾以外的位置频繁插入或删除元素的操作,deques 的性能较差 ,什么都可以,但什么都不精通。

相比vector:

相比list:

deque缺陷:

        1. 不适合随机访问

        2. 不适合高频访问

        3. 不适合中间位置插入删除

        所以这个容易并不常用。

        所以deque相比vector、list更适合作为stack、queue的容器,stack、queue不会在中间位置插入删除,是高频的头尾操作。

 deque的实现很繁琐,有难度,它的迭代器封装了4个指针

deque设计初衷是替代vector、list的容器,但最终设计出来并没有很大优势,没有它们突出


总结

        综合deque的特点,stack、queue都十分适合采用适配deque容器,这是因为deque的设计特点。适配器不需要自己实现容器,所以stack、queue的代码量非常短,没有难点。下节,我们将学习优先级队列

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

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

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

相关文章

Linux之基本指令操作

1、whoami whoami&#xff1a;查看当前账号是谁 2、who who&#xff1a;查看当前我的系统当中有哪些用户&#xff0c;当前有哪些人登录了我的机器 3、 pwd pwd&#xff1a;查看我当前所处的目录&#xff0c;就好比Windows下的路径 4、ls ls&#xff1a;查看当前目录下的文件信…

算法导论6:摊还分析,显式与隐式

P258 摊还分析概念 聚合分析&#xff0c;利用它&#xff0c;我们证明对于n&#xff0c;一个n个操作的序列最坏情况下的花费的总时间为T(n)&#xff0c;因此&#xff0c;在最坏情况下&#xff0c;每个操作的平均代价&#xff08;摊还代价&#xff09;为T(n)/n 举了例子来形容这…

头歌答案Python——JSON基础

目录 ​编辑 Python——JSON基础 第1关&#xff1a;JSON篇&#xff1a;JSON基础知识 任务描述 第2关&#xff1a;JSON篇&#xff1a;使用json库 任务描述 Python——XPath基础 第1关&#xff1a;XPath 路径表达式 任务描述 第2关&#xff1a;XPath 轴定位 任务描述…

SOME/IP 协议介绍(四)RPC协议规范

RPC协议规范 本章描述了SOME/IP的RPC协议。 传输协议绑定 为了传输不同传输协议的SOME/IP消息&#xff0c;可以使用多种传输协议。SOME/IP目前支持UDP和TCP。它们的绑定在以下章节中进行了解释&#xff0c;而第[SIP_RPC_450页&#xff0c;第36页]节讨论了选择哪种传输协议。…

[C国演义] 第十八章

第十八章 最长斐波那契子序列的长度最长等差数列等差序列划分II - 子序列 最长斐波那契子序列的长度 力扣链接 子序列 ⇒ dp[i] — — 以 arr[i] 结尾的所有子序列中, 斐波那契子序列的最长长度子序列 ⇒ 状态转移方程 — — 根据最后一个位置的组成来划分 初始化 — — 根…

海外媒体发稿:彭博社发稿宣传中,5种精准营销方式

在如今的信息发生爆炸时期&#xff0c;营销方式多种多样&#xff0c;但是充分体现精准营销并针对不同用户群体的需求并非易事。下面我们就根据彭博社发稿营销推广为例子&#xff0c;给大家介绍怎样根据不同用户人群方案策划5种精准营销方式。 1.界定总体目标用户人群在制订精准…

Flink SQL 表值聚合函数(Table Aggregate Function)详解

使用场景&#xff1a; 表值聚合函数即 UDTAF&#xff0c;这个函数⽬前只能在 Table API 中使⽤&#xff0c;不能在 SQL API 中使⽤。 函数功能&#xff1a; 在 SQL 表达式中&#xff0c;如果想对数据先分组再进⾏聚合取值&#xff1a; select max(xxx) from source_table gr…

华为ensp搭建小型园区网络规划

文章目录 前言一、拓扑图二、数据规划三、设备配置四.配置命令1.配置接入层交换机ACC11.1 设备命名&#xff0c;创建VLAN1.2 配置eth-trunk 11.3 配置用户端 2.配置核心层交换机CORE2.1设备命名2.2配置Eth-Trunk2.3 vlan配置ip2.4 上行接口配置 3.DHCP配置3.1 CORE: 4.配置路由…

计算机毕业设计:疲劳驾驶检测识别系统 python深度学习 YOLOv5 (包含文档+源码+部署教程)

[毕业设计]2023-2024年最新最全计算机专业毕设选题推荐汇总 1、项目介绍 基于YOLOv5的疲劳驾驶检测系统使用深度学习技术检测常见驾驶图片、视频和实时视频中的疲劳行为&#xff0c;识别其闭眼、打哈欠等结果并记录和保存&#xff0c;以防止交通事故发生。本文详细介绍疲劳驾…

ROC 曲线详解

前言 ROC 曲线是一种坐标图式的分析工具&#xff0c;是由二战中的电子和雷达工程师发明的&#xff0c;发明之初是用来侦测敌军飞机、船舰&#xff0c;后来被应用于医学、生物学、犯罪心理学。 如今&#xff0c;ROC 曲线已经被广泛应用于机器学习领域的模型评估&#xff0c;说…

模板初阶 C++

目录 泛型编程 函数模板 概念 格式 原理 函数模板的实例化 类模板 格式 类模板的实例化 泛型编程 当我们要实现一个交换函数&#xff0c;我们可以利用函数重载实现&#xff0c;但是有几个不好的地方 1.函数重载仅仅是类型不同&#xff0c;代码复用率较低&#xff0c;只…

pyorch Hub 系列#4:PGAN — GAN 模型

一、主题描述 2014 年生成对抗网络的诞生及其对任意数据分布进行有效建模的能力席卷了计算机视觉界。两人范例的简单性和推理时令人惊讶的快速样本生成是使 GAN 成为现实世界中实际应用的理想选择的两个主要因素。 然而&#xff0c;在它们出现后的很长一段时间内&#xff0c;GA…

知识蒸馏概述及开源项目推荐

文章目录 1.介绍2.知识2.1 基于响应的知识&#xff08;response-based&#xff09;2.2 基于特征的知识(feature-based)2.3 基于关系的知识(relation-based) 3.蒸馏机制3.1 离线蒸馏3.2 在线蒸馏3.3 自蒸馏 4.教师-学生架构5.蒸馏算法5.1 对抗性蒸馏&#xff08;Adversarial Dis…

Linux基础开发工具之调试器gdb

文章目录 1.编译成的可调试的debug版本1.1gcc test.c -o testdebug -g1.2readelf -S testdebug | grep -i debug 2.调试指令2.0quit退出2.1list/l/l 数字: 显示代码2.2run/r运行2.3断点相关1. break num/b num: 设置2. info b: 查看3. d index: 删除4. n: F10逐过程5. p 变量名…

Python文件、文件夹操作汇总

目录 一、概览 二、文件操作 2.1 文件的打开、关闭 2.2 文件级操作 2.3 文件内容的操作 三、文件夹操作 四、常用技巧 五、常见使用场景 5.1 查找指定类型文件 5.2 查找指定名称的文件 5.3 查找指定名称的文件夹 5.4 指定路径查找包含指定内容的文件 一、概览 ​在…

CSS注入的四种实现方式

目录 CSS注入窃取标签属性数据 简单的一个实验&#xff1a; 解决hidden 方法1&#xff1a;jsnode.js实现 侧信道攻击 方法2&#xff1a;对比波兰研究院的方案 使用兄弟选择器 方法3&#xff1a;jswebsocket实现CSS注入 实验实现&#xff1a; 方法4&#xff1a;window…

【云备份|| 日志 day6】文件业务处理模块

云备份day6 业务处理 业务处理 云备份项目中 &#xff0c;业务处理模块是针对客户端的业务请求进行处理&#xff0c;并最终给与响应。而整个过程中包含以下要实现的功能&#xff1a; 借助网络通信模块httplib库搭建http服务器与客户端进行网络通信针对收到的请求进行对应的业…

算法导论笔记4:散列数 hash

一 了解一些散列的基本概念&#xff0c;仅从文字角度&#xff0c;整理了最基础的定义。 发现一本书&#xff0c;《算法图解》&#xff0c;微信读书APP可读&#xff0c;有图&#xff0c;并且是科普性质的读物&#xff0c;用的比喻很生活化&#xff0c;可以与《算法导论》合并起…

Xshell远程登录 Linux小键盘数字输入变成字母解决办法

Xshell的设置问题&#xff0c;依次查看&#xff1a;文件-->属性-->终端-->VT模式-->初始数字键盘模式更改为&#xff1a;设置普通&#xff08;s&#xff09;

vue-常用指令

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容-常用指令 目录 常用指令 1、v-cloak 2、数据绑定指令 3、v-once 4、v-bind&#xff08;重点&a…