今天学栈:原来“先进后出”里藏着这么多门道
抱着“不就是个存东西的容器吗”的想法走进数据结构课,结果被老师手里的一摞盘子彻底颠覆了认知——原来栈这东西,看着简单,实际理解和动手实现时,藏着不少“反直觉”的细节。
老师开篇没讲代码,先把盘子一个个往上叠,说“要拿最上面的盘子,必须先把上面的挪走”,这就是栈的“先进后出(LIFO)”。我当时还觉得“这不废话吗”,直到老师在黑板上画了栈的示意图:栈底固定,栈顶随元素增减移动,push是“压栈”、pop是“弹栈”,连栈空、栈满都有明确的判断条件,才发现之前想的太浅了——栈不是随便堆东西,而是有严格规则的“有序容器”。
接着动手用Java实现栈,第一个坎就来了:用数组还是链表?老师先带我们写数组实现的栈,定义了int[] arr
存元素,top
变量记录栈顶索引。写push
方法时,得先判断“栈满了吗?”(top == arr.length - 1
),满了要扩容;写pop
方法时,又得先判断“栈空了吗?”(top == -1
),空了还得抛异常。我一开始没加判断,直接往满了的栈里push,结果数组越界报错,才明白“边界处理”是栈的核心——就像叠盘子不能叠到塌,拿盘子也不能拿空盘子,规则必须守死。
后来老师讲栈的应用,更是让我眼前一亮。最经典的括号匹配问题,之前自己想的时候总觉得要“一对对找”,没想到用栈这么简单:遇到左括号就push,遇到右括号就pop出栈顶元素比对,最后看栈是不是空的。老师现场写了代码,比如处理“{[( )]}”时,栈里依次压入{
、[
、(
,遇到)
就弹出(
,遇到]
弹出[
,遇到}
弹出{
,最后栈空,说明匹配成功。我跟着敲代码时,看着控制台输出“括号匹配正确”,突然懂了“数据结构是为算法服务”——没有栈,这种问题处理起来要麻烦好几倍。
还有表达式求值(比如“3+4*2-5”),老师说栈能帮我们处理运算符的优先级:用两个栈,一个存数字,一个存运算符,遇到优先级高的运算符就先压栈,遇到低的就先计算栈顶的表达式。虽然这部分没完全吃透,但大概能感觉到栈的“秩序感”——把混乱的表达式按规则拆成栈里的元素,再一步步计算,比硬算条理多了。
下课前翻着自己写的ArrayStack
类,从push
、pop
到peek
(查看栈顶元素),每个方法都围绕“先进后出”的规则,突然觉得数据结构不是“纸上谈兵”。之前学Java类和方法是“练基本功”,现在学栈,更像是“学怎么用基本功搭一个有用的工具”——这个工具能帮我们解决括号匹配、表达式求值这些实际问题,比单纯写个计算方法有意思多了。
晚上打算再试试用链表实现栈,对比下数组栈和链表栈的区别。原来学数据结构,不是记定义,而是理解“为什么要这么设计”“能用来解决什么问题”——今天这堂栈的课,算是给我打开了数据结构的第一扇小窗。