想象一下,你每天早上用的盘子,刚洗完的盘子会叠放在一起。最上面的盘子是你刚放上去的,要拿盘子的时候,你必须先把最上面的那个拿走,对吧?这个“先放的在下面,后放的在上面,拿的时候先拿上面的”的过程,其实就是我们今天要讲的数据结构——栈的核心特性。
一、栈是什么?¶
栈(Stack)是一种特殊的线性表,它的特殊之处在于:只能从一端进行插入和删除操作。我们把这个操作的一端叫做“栈顶”,另一端叫做“栈底”。就像叠盘子,栈底是最下面的盘子,栈顶是最上面的盘子;只能在栈顶放盘子(入栈)或拿盘子(出栈),不能从栈底操作。
二、核心特性:后进先出(LIFO)¶
栈最关键的特性是“后进先出”(Last In First Out,简称LIFO)。意思是:最后放入栈的元素,会最先被取出。
举个生活例子:
- 你把弹夹里的子弹一颗颗压进去,最后压进去的子弹在最上面。开枪时,你会先射出最上面的那颗子弹(最后压进去的),这就是“后进先出”。
- 叠被子:周一叠的被子在最下面,周二叠的在上面,你整理时会先叠周二的被子(后叠的),这也是LIFO。
对比一下队列(先进先出,FIFO):排队买票时,先到的人先买到票(先入先出);而栈就像“插队”,最后插队的人反而先被处理。
三、栈的基本操作¶
栈只有两个核心操作:入栈(push) 和 出栈(pop),还可以查看栈顶元素(top)或判断栈是否为空(empty)。
- 入栈(push):往栈顶添加一个元素。
比如:你往盘子堆里放一个新盘子,这个盘子会成为新的栈顶。 - 出栈(pop):从栈顶移除一个元素,并返回该元素。
比如:你从盘子堆里拿走最上面的盘子,这个盘子就是栈顶元素,拿走后栈顶变成下一个盘子。 - 查看栈顶(top):不删除元素,只看栈顶的元素(比如你想看看最上面的盘子是什么样的)。
- 判空(empty):检查栈里是否还有元素(比如你想知道盘子堆是否空了)。
四、栈的原理图解(用例子理解)¶
假设我们按顺序执行以下操作,用数字标记入栈顺序,用箭头表示出栈顺序:
-
初始状态:栈为空(用
[]表示,[]中左边是栈底,右边是栈顶)。
栈:[] -
入栈1(push 1):往栈顶加1,此时栈顶是1。
栈:[1] -
入栈2(push 2):往栈顶加2,此时栈顶是2(1在栈底,2在栈顶)。
栈:[1, 2] -
入栈3(push 3):往栈顶加3,此时栈顶是3(顺序:1→2→3,栈底到栈顶)。
栈:[1, 2, 3] -
出栈(pop):从栈顶拿走3,此时栈顶变成2。
栈:[1, 2]
(弹出元素:3) -
出栈(pop):从栈顶拿走2,此时栈顶变成1。
栈:[1]
(弹出元素:2) -
出栈(pop):从栈顶拿走1,此时栈为空。
栈:[]
(弹出元素:1)
五、栈的实际应用场景¶
栈在编程和生活中无处不在,以下是几个经典例子:
- 括号匹配:比如判断表达式
(a + b) * (c - d)是否合法。用栈记录左括号,遇到右括号就弹出栈顶的左括号,最后栈空则合法(否则括号不匹配)。 - 函数调用栈:当你写一个程序时,调用
main函数,main里调用func1,func1里调用func2,func2执行完后会先返回func1,func1执行完返回main。这就是栈的“后进先出”特性(后调用的函数先返回)。 - 浏览器后退功能:你打开网页1→网页2→网页3,后退时会依次回到网页2→网页1,相当于弹出栈顶元素(网页3→网页2→网页1)。
- 表达式求值:比如计算
3 + 4 * 2 / (1 - 5),需要用栈处理运算符优先级,先算括号、再乘除、最后加减。
六、总结¶
栈是一种“后进先出”的线性结构,只能从栈顶操作元素。它的核心是“LIFO”,在括号匹配、函数调用、浏览器后退等场景中发挥关键作用。记住:栈就像叠盘子,后放的先拿,先放的后拿。
理解栈的基本操作和特性后,你会发现它是解决很多问题的“利器”,比如后续学习递归、动态规划时,栈也会经常被用到。
(PS:如果觉得抽象,可以拿个笔筒模拟:把笔从下往上依次插入,每次拿最上面的笔,就是栈的“后进先出”啦!)