当前位置: 首页 > news >正文

stack的详细介绍,queue的详细介绍

stack前言

在上一篇文章中,我们首先提出了三个问题:

  1. C++中stack 是容器么?
  2. stack 提供迭代器来遍历stack空间么?
  3. 我们使用的STL中stack是如何实现的?

我们的回答是:

  1. stack不是容器,而是容器适配器
  2. satck不提供迭代器来遍历stack空间。
  3. c++中,stack的底层实现默认使用deque。

在上一篇文章中有对这三个问题更详细地回答,并且有对deque底层实现的详细介绍。相信了解了deque之后对于学习stack会更有帮助!

deque(双端队列)底层实现和实际运用-CSDN博客

stack 在 deque 的基础上主要做了以下几件事情
 

  1. 限制了功能接口: stack 并没有实现自己的数据结构,而是适配了底层的容器(默认是 deque)。它只对外暴露了栈这种数据结构应该有的基本操作,强制执行了栈的后进先出 (LIFO) 的原则,隐藏了底层容器的许多其他功能
     
  2. 提供了更清晰的语义: 使用 stack 可以更清晰地表达代码的意图。当你看到代码中使用 stack 时,你会立即知道这里需要的是一个栈这种数据结构,而不是一个更通用的双端队列。这提高了代码的可读性和可维护性。

stack的函数接口

好消息:由于stack功能比较简单,所以stack提供的函数接口很少!!!

一定要注意:对于栈来说,是不向我们提供任何迭代器的,我们也就不能使用迭代器来遍历栈空间。

stack向我们提供的函数接口也比较少,常见的就是下面的几个:

  • top(): 返回栈顶元素的引用。
  • push(): 将一个新元素添加到栈顶,返回值类型: void。
  • pop(): 移除位于栈顶的元素,返回值类型: void。
  • empty(): 检查栈是否为空,空的时候返回true。
  • size(): 返回栈中当前元素的数量。

栈(stack)是容器适配器,容器适配器(stack/queue)中,添加元素函数名都是push,移除元素都是pop,在vector以及deque中函数名都是push_back,pop_back。

#include<iostream>//c++标准头文件,可以使用cout,cin等标准库函数 
#include<stack>//使用stack时需要的头文件 
using namespace std;//命名空间,防止重名给程序带来各种隐患,使用cin,cout,stack,map,set,vector,queue时都要使用
int main(){
	stack<int> s;//定义一个int类型的stack
	
	s.push(1);//往栈里放入一个元素1
	s.push(2);//往栈里放入一个元素2
	s.push(3); //往栈里放入一个元素3
	
	cout<<"按顺序放入元素1、2、3后,目前栈里的元素:1 2 3" <<endl;
	cout<<"s.size()="<<s.size()<<endl;//s.size()返回栈内元素的个数  
	cout<<"s.empty()="<<s.empty()<<endl; //判断栈是否为空,值为1代表空,0代表非空,用s.size()同样可以判断 ,s.size()的值为0就代表空的 
	cout<<"s.top()="<<s.top()<<endl;//查看栈顶的元素 
	cout<<endl;
	
	s.pop();//弹出栈顶元素 
	cout<<"s.pop()后,目前栈里的元素:1 2"<<endl;
	cout<<"s.size()="<<s.size()<<endl;
	cout<<"s.empty()="<<s.empty()<<endl; 
	cout<<"s.top()="<<s.top()<<endl;
	cout<<endl;
	
	
	s.pop();
	cout<<"s.pop()后,目前栈里的元素:1"<<endl;
	cout<<"s.size()="<<s.size()<<endl;
	cout<<"s.empty()="<<s.empty()<<endl; 
	cout<<"s.top()="<<s.top()<<endl;
	cout<<endl;
	 
	s.pop();
	cout<<"s.pop()后,目前的栈是空的"<<endl;
	cout<<"s.size()="<<s.size()<<endl;
	cout<<"栈是空的就不能用s.top()访问栈顶元素了" <<endl; 
	cout<<"s.empty()="<<s.empty()<<endl; 
	 
}

运行结果: 

按顺序放入元素1、2、3后,目前栈里的元素:1 2 3
s.size()=3
s.empty()=0
s.top()=3

s.pop()后,目前栈里的元素:1 2
s.size()=2
s.empty()=0
s.top()=2

s.pop()后,目前栈里的元素:1
s.size()=1
s.empty()=0
s.top()=1

s.pop()后,目前的栈是空的
s.size()=0
栈是空的就不能用s.top()访问栈顶元素了
s.empty()=1

stack实际运用

栈因其简单而强大的后进先出(LIFO) 特性,在计算机科学中扮演着重要的角色。无论是解决算法难题,还是作为构建更复杂数据结构的基础,栈都是一个非常有用的工具。下面介绍栈的常见的几种使用情景:

表达式求值 (Expression Evaluation):

  • 中缀表达式转后缀表达式 (Infix to Postfix Conversion): 栈可以用来存储操作符,并根据运算符优先级和括号来生成后缀表达式(也称为逆波兰表示法)。
  • 后缀表达式求值 (Postfix Evaluation): 栈可以用来存储操作数。当遇到操作符时,从栈中弹出所需数量的操作数进行计算,并将结果压回栈中。

括号匹配 (Parentheses Matching):

  • 判断一个字符串中的括号(例如 (), [], {}) 是否正确匹配。遍历字符串,遇到左括号则压入栈中,遇到右括号则检查栈顶是否是对应的左括号。如果匹配则弹出栈顶元素,否则或栈为空时遇到右括号则表示不匹配。最后栈为空则表示所有括号都匹配。

深度优先搜索 (Depth-First Search, DFS):

  • 在图或树的遍历中,DFS 通常使用栈(可以是显式的 std::stack,也可以是递归调用的隐式栈)来跟踪访问过的节点和待访问的邻居节点。比如对于二叉树的遍历中就是可以使用栈来实现二叉树的前序,中序,后序遍历。

回溯算法 (Backtracking):

  • 许多回溯算法(例如解决迷宫问题、N 皇后问题、子集生成等)都使用栈来保存当前的状态。当探索到一条死路时,可以从栈中弹出最近的状态进行回溯,尝试其他路径。

实现递归 (Implicitly):

  • 递归函数的执行本质上依赖于系统维护的函数调用栈。理解栈的工作方式有助于理解递归的原理。虽然你通常不会显式地用 std::stack 来“实现”递归,但你可以使用栈来将某些递归算法转换为迭代算法,以避免递归深度过大的问题。

深入探讨一下使用栈来实现递归

理论上来说,所有用递归能解决的问题都可以用栈(通常是通过迭代的方式模拟)来解决。

这是因为递归的本质就是通过系统维护的调用栈 (Call Stack) 来实现的。每次进行递归调用时,当前函数的局部变量、参数、返回地址等信息会被压入调用栈中。当递归调用返回时,这些信息会从栈中弹出,程序会回到上一次函数调用的位置继续执行。


 

实际上来说,递归方式的函数栈可以自动帮助我们来保存信息,保存的信息很有条理。

我们自己想用迭代+栈来实现的话,要考虑的问题就比较多,需要考虑

  • 何时入栈出栈,
  • 多次入栈出栈,
  • 还要考率栈为空时对应的逻辑,
  • 有的时候需要多个栈来实现简单递归就能实现的问题,
  • 不容易实现。

        虽然用迭代加栈来理解递归的本质很有帮助,但在实际开发中,我们通常会优先选择更自然、更易于理解和维护的实现方式。对于那些容易产生栈溢出的递归,或者对性能有极致要求的场景,才会考虑使用迭代加栈的方式进行优化或替代。

对于栈的一个问题解答

我们在了解完栈之后,可以显著发现栈的所有功能其实很简单,成员函数也不多,deque,vector的功能其实完全可以覆盖栈的功能,那为什么我们很多时候还是会选择使用栈呢?

从功能上讲,dequevector 的确可以用来模拟栈的行为。你可以只使用它们的尾部进行添加和删除操作,从而实现栈的 pushpop 功能。

然而,我们很多时候仍然选择使用 std::stack,这主要是出于以下几个重要的原因:

代码的意图和可读性 (Intent and Readability):

  • std::stack 这个名字本身就清晰地表明了代码的意图:这里需要一个后进先出 (LIFO) 的数据结构。当其他开发者(或者未来的你)阅读这段代码时,一眼就能明白这里使用的是栈的语义。
  • 如果你直接使用 dequevector 并只操作尾部,虽然功能上实现了栈,但代码的意图并不那么明确。可能会让人疑惑为什么选择 dequevector,是否还有其他操作会用到它们的其他功能。使用 std::stack 则消除了这种歧义。

制执行栈的语义 (Enforcing Stack Semantics):

  • std::stack 的接口被有意地限制为只包含栈的基本操作 (push, pop, top, empty, size, emplace, swap). 这种限制避免了开发者在不经意间使用了底层容器的其他功能(比如 dequepush_front, pop_front, 随机访问等,或者 vector 的随机访问),从而破坏了栈的 LIFO 原则。

综合来看:使用 stack 更多的是出于代码清晰性语义明确性强制执行栈原则以及提高代码可维护性的考虑。它是一种更符合逻辑和更安全的选择,能够更好地表达程序的意图。

queue前言

在了解完 stack 之后,我们现在来看一下 queue。首先提出三个类似的问题:

  1. C++ 中 queue 是容器么?
  2. queue 提供迭代器来遍历 queue 空间么?
  3. 我们使用的 STL 中 queue 是如何实现的?

我们的回答是:

  1. queue 不是容器,而是容器适配器。
  2. queue 不提供迭代器来遍历 queue 空间。
  3. C++ 中,queue 的底层实现默认使用 deque。

 在上一篇文章中有对这三个问题更详细地回答,并且有对deque底层实现的详细介绍。相信了解了deque之后对于学习queue会更有帮助!

deque(双端队列)底层实现和实际运用-CSDN博客

queue 在 deque 的基础上主要做了以下几件事情:

  • 限制了功能接口: queue 并没有实现自己的数据结构,而是适配了底层的容器(默认是 deque)。它只对外暴露了队列这种数据结构应该有的基本操作,强制执行了队列的先进先出 (FIFO) 的原则,而隐藏了底层容器的许多其他功能
  • 提供了更清晰的语义: 使用 queue 可以更清晰地表达代码的意图。当你看到代码中使用 queue 时,你会立即知道这里需要的是一个队列这种数据结构,而不是一个更通用的双端队列。这提高了代码的可读性和可维护性。

queue的函数接口

和stack(栈)一样,queue也是一个容器适配器,所以queue也是不向我们提供任何迭代器的,我们也就不能使用迭代器来遍历队列空间。

queue向我们提供的函数接口也比较少,常见的就是下面的几个:

  • front(): 返回队列头部元素的引用。
  • back(): 返回队列尾部元素的引用。
  • push(): 将一个新元素添加到队列的尾部,返回值类型: void。
  • pop(): 移除位于队列头部的元素,返回值类型: void。
  • empty(): 检查队列是否为空,空的时候返回true。
  • size(): 返回队列中当前元素的数量。

 队列(queue)是容器适配器,容器适配器(stack/queue)中,添加元素函数名都是push,移除元素都是pop,在vector以及deque中函数名都是push_back,pop_back。

queue实际运用

queue 在实际算法编程中有着广泛的应用,其核心特性是先进先出 (FIFO),这使得它非常适合处理需要按顺序处理元素的场景。以下是一些常见的运用:

1. 广度优先搜索 (Breadth-First Search, BFS):(最常见也是最重要的应用

  • 典型场景: 在无权图中查找两个节点之间的最短路径。
  • 原理: BFS 从起始节点开始,首先访问其所有直接邻居,然后访问这些邻居的邻居,依此类推,逐层扩展。std::queue 非常适合用来存储待访问的节点,保证了按层级顺序进行探索。

2. 树的层序遍历 (Level Order Traversal):

  • 典型场景: 按照树的层级顺序访问所有节点。
  • 原理: 将根节点入队,然后当队列不为空时,取出队首节点并访问它,接着将其所有子节点按从左到右的顺序入队。重复这个过程,直到队列为空。

3. 任务调度 (Task Scheduling):

  • 典型场景: 模拟任务按照到达顺序被处理的情况。
  • 原理: 将待处理的任务放入队列中,然后按照入队顺序依次取出任务进行处理。这保证了先到达的任务先被执行。

4. 消息队列 (Message Queues):

  • 典型场景: 在不同的程序或线程之间传递消息。
  • 原理: 发送者将消息放入队列,接收者从队列中取出消息进行处理。队列保证了消息的顺序性和可靠性。虽然实际的消息队列系统可能更复杂,但 std::queue 提供了一个基本的模型。

对比一下stack和queue

函数接口方面

queue中的函数接口和stack中的差不多,stack常用的函数接口有5个,queue常用的函数接口有6个。

  • stack中返回栈顶元素是top();
  • queue中返回队列头部元素的引用是front(),返回队列尾部元素的引用时back()。

  • stack将一个新元素添加到栈顶函数接口为push(),将一个元素从栈顶移除的接口为pop();
  • queue将一个新元素添加到队列尾部函数接口为push(),将一个元素从队列头部移除的接口为pop()。

对于empty() 和 size()这两个函数接口来说,stack和queue使用方法一样。

容器适配器功能方面:

这两个容器适配器默认的底层实现使用的都是deque(双端队列)

STL标准库对于stack和queue来说,都没有提供迭代器来对其进行访问。

stack是对同一个口进行push和pop操作,queue是对两个口中的尾部进行push操作,对头部进行pop操作。

http://www.dtcms.com/a/98831.html

相关文章:

  • 转发和重定向的区别详解
  • Java的string默认值
  • ffuf:一款高效灵活的Web模糊测试利器
  • 右值和右值引用【C++】
  • onlyoffice 多核心研究
  • 763划分字母区间解题记录
  • java基础:常见类和对象
  • 游戏被外挂攻破?金融数据遭篡改?AI反作弊系统实战方案(代码+详细步骤)
  • Linux|gitlab|二进制快速安装部署gitlab-ce教程
  • 19_20 js es6
  • std::countr_zero
  • 模型苏醒计划:Threejs 让静态模型「叛逆」起来
  • Java.util.logging (JUL) 终极指南:从基础配置到高级玩法
  • 外观模式_结构型_GOF23
  • 游戏引擎学习第192天
  • 第三卷:覆舟山决战(73-108回)正反人物群像
  • 习题1.43
  • 软件工程面试题(十一)
  • 【数据结构】队列
  • el-radio-group 中 el-radio-button value未能绑定上数值数据
  • 欢乐力扣:合并两个有序链表
  • Redis6数据结构之List类型
  • 25_闭包节流防抖
  • Gateway实战(三)、断言-时间、Cookie信息
  • 从零开始研发GPS接收机连载——16、接收天上卫星信号成功定位
  • Python之变量与数据类型总结
  • Linux C语言调用第三方库,第三方库如何编译安装
  • Android 12系统源码_输入系统(四)触摸异常问题排查
  • nginx 设置隐藏版本号
  • 【LangChain入门 9 Agent 】LangChain开发Agent智能体