栈
定义
栈,是一种「后进先出」的线性数据结构。
理解
栈,只有一端能够进出元素,一般称这一端为栈顶,另一端为栈底。
添加或者删除栈中元素时,只能将其插入到栈顶(即入栈操作),或者把栈顶元素从栈中取出(即出栈操作)。
代码模板
涉及到栈的操作,可以方便地利用标准模板库 STL 中的 stack 实现。
1 | |
具体应用
括号匹配
在一个表达式中,会含有圆括号、方括号或者花括号等来表示运算的优先级,将这些括号单独提取出来就构成了括号序列。
合法的括号序列称为匹配序列,不合法的括号序列称为不匹配序列。
匹配的括号序列需要满足以下条件:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
例如:([()])、[]{}()()[()] 是匹配的括号序列,([()]](}[()] 是不匹配的括号序列。
给定一个只包含 (,),{,},[,] 的字符串,判断字符串是否有效。
从左至右扫描字符串,当遇到一个左括号时,期望后续能够有一个对应的右括号与之匹配。由于后遇到的左括号要先匹配,与栈的「后进先出」思想一致,因此可以借助栈验证括号序列是否匹配。
- 初始化:创建一个空的字符栈。
- 从左至右扫描字符串,逐个检查字符串中的每个字符。
如果是左括号,将其压入栈。
如果是右括号,尝试与栈顶字符进行匹配(如果是与之匹配的左括号,则弹出栈顶字符;否则,该括号序列不合法)。- 如果全部字符遍历完毕,栈中仍然存在字符(还有左括号未匹配),则该序列不合法;否则,该括号序列不合法。
1 | |
验证栈序列
初始有一个空栈。现在给定两个序列 in 和 out,其中 in 表示入栈序列,out 表示出栈序列,两个序列中都没有重复元素。判断入栈序列 in 能否通过若干次的入栈和出栈,得到出栈序列 out。
由于两个序列中都没有重复元素,如果入栈序列和出栈序列匹配,则每个元素出栈的位置是唯一的,因此每个元素只会入栈一次并且出栈一次。
初始化两个变量 A 和 B,分别指向入栈序列 in 的第一个元素和出栈序列 out 的第一个元素。遍历入栈序列 in,从头开始依次匹配出栈序列 out 的各个元素。循环执行如下操作:
- 如果入栈序列中当前元素 in[A] 等于出栈序列中待匹配的元素 out[B],则该元素先入栈并且立即出栈,变量 A++,B++。
- 否则,如果栈非空,判断栈顶的元素是否等于出栈序列中待匹配的元素 out[B]。如果相等,则该元素出栈,变量 B++。
- 如果以上两种情况都不符合,但入栈序列 in 中仍有元素可以入栈,则与出栈序列中待匹配的元素 out[B] 相等的元素可能还未入栈,因此先将入栈序列中当前元素 in[A] 入栈,变量 A++。
- 如果既无法完成匹配,并且入栈序列中的所有元素都已经入栈,则匹配失败,循环结束。
1 | |
计算后缀表达式的值
中缀表达式:最常见的表达式,格式为「A op B」,其中 op 是一个运算符,A 和 B 都是中缀表达式,例如:5 * (8 - 4)
前缀表达式:又称波兰式,格式为「op A B」,例如:* 5 - 8 4
后缀表达式:又称逆波兰式,格式为「A B op」,例如:8 4 - 5 *
前缀表达式和后缀表达式消除了对运算符优先级的依赖,也不需要使用括号来表示运算的顺序,这使得计算变得更为直接和高效。
给定一个后缀表达式,其中运算符仅包含 +、-、*、/,保证对于 / 运算除数不为 0,计算后缀表达式的值。
计算后缀表达式的值,可以借助栈快速实现。
- 初始化:创建一个空的数值栈。
- 从左至右扫描表达式,逐个检查表达式中的每个元素。
如果是操作数,将其压入栈。
如果是运算符,从栈中弹出两个操作数,执行相应的运算(次栈顶元素 op 栈顶元素),然后将结果压回栈中。- 最终结果:当表达式中的所有元素都被处理后,留在栈中的元素就是整个表达式的值。
例如:8 4 - 5 *
- 初始化一个空栈:[](从左至右依次是栈底到栈顶)
- 扫描表达式:
- 8:入栈 [8]
- 4:入栈 [8 4]
- -:弹出 8 和 4,计算 8 - 4 = 4,入栈 [7]
- 5:入栈 [7 5]
- *:弹出 7 和 5,计算 7 * 5 = 35,入栈 [35]
- 最终结果:35