如何理解“栈”?

关于“栈”,我有一个非常贴切的例子,就是一摞叠在一起的盘子。我们平时放盘子的时候,都是从下往上一个一个放;取的时候,我们也是从上往下一个一个地依次取,不能从中间任意抽出。后进者先出,先进者后出,这就是典型的“栈”结构。

从栈的操作特性上来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据

我第一次接触这种数据结构的时候,就对它存在的意义产生了很大的疑惑。因为我觉得,相比数组和链表,栈带给我的只有限制,并没有任何优势。那我直接使用数组或者链表不就好了吗?为什么还要用这个“操作受限”的“栈”呢?

事实上,从功能上来说,数组或链表确实可以替代栈,但你要知道,特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。

当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。

支持动态扩容的顺序栈

如果要实现一个支持动态扩容的栈,我们只需要底层依赖一个支持动态扩容的数组就可以了。当栈满了之后,我们就申请一个更大的数组,将原来的数据搬移到新数组中。

实际上,支持动态扩容的顺序栈,我们平时开发中并不常用到。

入栈、出栈的时间复杂度:

对于出栈操作来说,我们不会涉及内存的重新申请和数据的搬移,所以出栈的时间复杂度仍然是 O(1)。但是,对于入栈操作来说,情况就不一样了。当栈中有空闲空间时,入栈操作的时间复杂度为 O(1)。但当空间不够时,就需要重新申请内存和数据搬移,所以时间复杂度就变成了 O(n)。

也就是说,对于入栈操作来说,最好情况时间复杂度是 O(1),最坏情况时间复杂度是 O(n)。那平均情况下的时间复杂度又是多少呢?

如果当前栈大小为 K,并且已满,当再有新的数据要入栈时,就需要重新申请 2 倍大小的内存,并且做 K 个数据的搬移操作,然后再入栈。但是,接下来的 K-1 次入栈操作,我们都不需要再重新申请内存和搬移数据,所以这 K-1 次入栈操作都只需要一个 simple-push 操作就可以完成。

你应该可以看出来,这 K 次入栈操作,总共涉及了 K 个数据的搬移,以及 K 次 simple-push 操作。将 K 个数据搬移均摊到 K 次入栈操作,那每个入栈操作只需要一个数据搬移和一个 simple-push 操作。以此类推,入栈操作的均摊时间复杂度就为 O(1)。

通过这个例子的实战分析,也印证了前面讲到的,均摊时间复杂度一般都等于最好情况时间复杂度。因为在大部分情况下,入栈操作的时间复杂度 O 都是 O(1),只有在个别时刻才会退化为 O(n),所以把耗时多的入栈操作的时间均摊到其他入栈操作上,平均情况下的耗时就接近 O(1)。

栈在括号匹配中的应用

我们可以借助栈来检查表达式中的括号是否匹配。

我们同样简化一下背景。我们假设表达式中只包含三种括号,圆括号 ()、方括号[]和花括号{},并且它们可以任意嵌套。比如,{[] ()[{}]}或[{()}([])]等都为合法格式,而{[}()]或[({)]为不合法的格式。那我现在给你一个包含三种括号的表达式字符串,如何检查它是否合法呢?

这里也可以用栈来解决。我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。

当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。

如何实现一个“栈”?

从刚才栈的定义里,我们可以看出,栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。理解了栈的定义之后,我们来看一看如何用代码实现一个栈。

实际上,栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。

不管是顺序栈还是链式栈,我们存储数据只需要一个大小为 n 的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。

注意,这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。因为,这 n 个空间是必须的,无法省掉。所以我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。

空间复杂度分析是不是很简单?时间复杂度也不难。不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是 O(1)。

在Python中,我们可以通过数组或者链表的方式来实现栈,接下来先来看一下通过数组的方式来实现栈:

class Stack:def __init__(self):self.items = []def push(self, item):self.items.append(item)def pop(self):return self.items.pop()def peek(self):return self.items[-1]def is_empty(self):return len(self.items) == 0def size(self):return len(self.items)

  这个示例代码实现了一个栈类。它包括以下方法:

  ·__init__:创建一个空的栈。

  ·push:向栈中添加元素。

  ·pop:从栈中弹出元素,并将其返回。

  ·peek:返回栈顶元素,但不弹出它。

  ·is_empty:判断栈是否为空。

  ·size:返回栈中元素的数量。

  下面是使用链表的方式来实现栈:

class Node:def __init__(self, data=None, next=None):self.data = dataself.next = nextclass Stack:def __init__(self):self.head = Nonedef push(self, data):new_node = Node(data)new_node.next = self.headself.head = new_nodedef pop(self):if self.head is None:return Nonepopped = self.head.dataself.head = self.head.nextreturn poppeddef peek(self):if self.head is None:return Nonereturn self.head.datadef is_empty(self):return self.head is Nonedef size(self):count = 0node = self.headwhile node:count += 1node = node.nextreturn count

  这个示例代码也实现了一个栈类,它是通过链表来实现栈。它包括以下方法:

  ·__init__:创建一个空的栈。

  ·push:向栈中添加元素。

  ·pop:从栈中弹出元素,并将其返回。

  ·peek:返回栈顶元素,但不弹出它。

  ·is_empty:判断栈是否为空。

  ·size:返回栈中元素的数量。

  这两个实现方法的主要区别在于底层数据结构的不同,数组实现更容易理解,但在某些情况下可能会导致性能问题,而链表实现则更灵活,但在一些情况下可能会稍微复杂一些。

栈是什么意思?怎样实现一个栈?相关推荐

  1. 【数据结构与算法】详解什么是栈,并用代码手动实现一个栈结构

    本系列文章[数据结构与算法]所有完整代码已上传 github,想要完整代码的小伙伴可以直接去那获取,可以的话欢迎点个Star哦~下面放上跳转链接 https://github.com/Lpyexplo ...

  2. PTA 栈 (20分)(全网首发)(实现一个栈Stack,要求实现Push(出栈)、Pop(入栈)、Min(返回最小值的操作)的时间复杂度为O(1))

    题目描述: 我们知道平凡的栈有几个操作: push(value) 将 value 压入栈 pop() 将栈顶元素弹出, 并返回这个弹出的元素. 现在我们想要在平凡栈的基础上实现以下几个操作: push ...

  3. 栈和队列之用一个栈实现另一个栈的排序

    用一个栈实现另一个栈的排序 题目: 一个栈元素的类型为整数,现在要想将该栈从顶到底按从大到小的顺序排列,只允许申请一个栈,除此之外, 可以申请一个变量,可以申请额外的变量,但是不能申请额外的数据结构, ...

  4. 【左程云Java算法】Chapter1-5:用一个栈实现另一个栈的排序

    [左程云Java算法]Chapter1-5:用一个栈实现另一个栈的排序 [题目] 用一个栈实现另一个栈的排序 [要求] 一个栈中元素的类型为整型,现在想将该栈从顶到底按从大到小的顺序排序,只许申请一个 ...

  5. 栈与队列4——用一个栈实现另一个栈的排序

    题目 一个栈A的元素类型为整形,仅使用一个栈B,来实现栈A从栈顶到栈底元素是从大到小的顺序. 思路 原来的栈为stack,申请的栈为help,当前stack的栈顶元素记为cur 如果cur小于help ...

  6. 栈与队列3——用递归和栈操作逆序一个栈

    题目 一个栈依次压入1,2,3:此时栈顶到栈底元素分别为:3,2,1:将栈反转,使得栈顶到栈底元素为:1,2,3,仅限递归函数,并且不能使用其他数据结构 思路 使用两个函数reverse和getAnd ...

  7. 创建一个栈存储结构,并且写入一些对栈的基本的操作

    什么是栈:简单的说就是一个容器,一个瓶子,我们向瓶子里面放糖果,当想吃糖果的时候,将瓶子里的糖果再倒出来.我们要实现的就是怎样创建一个瓶子,并且怎样很容易的向里面添加糖果,向吃糖果的时候可以拿出来,自 ...

  8. python 判断括号是否匹配_使用Python实现一个栈判断括号是否平衡

    栈(Stack)在计算机领域是一个被广泛应用的集合,栈是线性集合,访问都严格地限制在一段,叫做顶(top). 举个例子,栈就想一摞洗干净的盘子,你每次取一个新盘子,都是放在这一摞盘子的最上头,当你往里 ...

  9. 如何仅用递归函数和栈操作逆序一个栈——你要先用stack实现,再去改成递归——需要对递归理解很深刻才能写出来...

    /**  * 如何仅用递归函数和栈操作逆序一个栈  * 题目:  * 一个栈依次压入1,2,3,4,5,那么从栈顶到栈底分别为5,4,3,2,1.  * 将这个栈转置后,从栈顶到栈底为1,2,3,4, ...

  10. 用一个栈实现另一个栈的排序

    要求: 在一个栈中元素的类型为整型,现在想将该栈从栈顶到栈底按从大到小的顺序排序,只许申请一个栈,除此之外,可以申请其他变量,但是不能申请额外的数据结构 解题思路: 待排序的栈stack, 辅助栈he ...

最新文章

  1. Microbiome:宏基因组分箱流程MetaWRAP分析实战和结果解读
  2. Java迭代器Iterator
  3. python核心理念_《三天搞定Python基础概念之第一天》中文版
  4. Windows Server 2008 多元密码策略之ADSIEDIT篇
  5. mysql数据库中实现内连接、左连接、右连接
  6. matlab计算abc三相短路电流_电工都知道三相电动机按一千瓦两个电流计算 却不会计算公式...
  7. word打开wps文件乱码_word文件打不开,打开时遇到错误用文本修复器打开文件 WPS打开时是乱码...
  8. 【电脑小白】提高ppt矢量图导出分辨率
  9. mysql 字段 decimals_[转]分析MySQL数据类型的长度【mysql数据字段 中length和decimals的作用!熟悉mysql必看】...
  10. 工作中需要MP4视频文件怎么办?用Python随手采集一些【订阅赠品】
  11. vue--实现跑马灯效果
  12. 动态gif图如何裁剪?一个小窍门教你在线裁剪动图
  13. 【iMessage苹果推信家庭推】位置推通过苹果实现iMessage群发的Apple script脚本代码如下: tell application “Messages” set csvDatator
  14. [转]马化腾:如何从“较好”到“最好”
  15. 阿里云数加案例-美柚
  16. 软件设计师备考(三)——UML(关系和UML图)
  17. 腾讯云轻量应用服务器搭建后端服务-基于django,nginx,uwsgi,supervisor
  18. cocosjs游戏前端大厅源码分析之启动场景
  19. Android studio 设置自动换行
  20. 【技巧】笔记本外接显示器配置教程(个人向)

热门文章

  1. 情迁QQ机器人执行SQL以及变量技巧详解
  2. crc校验c语言程序,C语言:CRC校验
  3. 什么是视距传播?如何实现高效微波点对点传输视距验证?
  4. 点云数据常用处理:C++实现
  5. 一起Talk Android吧(第三百七十九回:让ViewPager一屏幕显示三页)
  6. Awk的一些使用方法
  7. BeanDefinition 及其构造方式 BeanDefinitionBuilder, AbstractBeanDefinition
  8. 泰拉瑞亚装mod要java,泰拉瑞亚mod怎么安装教程 模组怎么进行安装
  9. 【测试工具】二、ab压力测试及结果分析
  10. AdobeRGB与sRGB详解