Python栈(list实现)
中等4用Python列表轻松实现栈(后进先出)
栈就像你桌上一叠作业本——你只能把新的本子放在最上面,拿作业时也只能从最上面拿。这种“后进先出”(Last In, First Out,简称LIFO)的规则,就是栈的核心特点。在Python里,我们不用自己写复杂的代码,直接用列表(list)的append和pop方法就能模拟栈,就像你伸手放本子和拿本子一样简单。
什么是栈?先看几个生活中的例子
栈在生活里随处可见,只是你没注意到它而已:
- 浏览器后退按钮:你访问了页面A、B、C。每次点击链接,新页面被“压”进栈顶。当你按“后退”时,先把C弹出来,再是B,最后是A。后看的页面先退出,完美符合栈规则。
- 自动售货机的商品弹簧:最后放进去的饮料会最先掉出来(弹簧从顶部推出)。
- 游戏里的“撤销”操作:你画了一笔,再画一笔,最后画的先被撤销(比如画图里的Ctrl+Z)。
- 学校里交作业:课代表把作业本叠成一摞,最后交的作业在最上面,老师改作业时从最上面拿。
这些例子的共同点:操作只发生在栈顶。你不能从中间抽一本作业,也不能把作业插到中间——否则作业堆就塌了(数据就乱了)。
用Python实现一个栈(列表版)
Python的列表天生适合做栈,因为它有:
append(元素)—— 相当于“放盘子到最上面”(压栈,push)pop()—— 相当于“拿走最上面的盘子”(出栈,pop,默认弹出最后一个元素)stack[-1]—— 看一眼最上面的盘子,但不拿走(获取栈顶元素)len(stack)—— 数一数有多少个盘子if stack:—— 判断盘子堆是不是空的
下面我们一步步模拟一个“零食罐子”,只能从顶部取放:
# 创建一个空栈,用来装零食
snack_stack = []
# 入栈:往罐子里放零食(按顺序放薯片、饼干、巧克力)
snack_stack.append("薯片") # 先放薯片
snack_stack.append("饼干") # 再放饼干,饼干在薯片上面
snack_stack.append("巧克力") # 最后放巧克力,在最高处
print("当前零食堆:", snack_stack)
# 输出:['薯片', '饼干', '巧克力'] (巧克力在栈顶)
# 出栈:从顶部拿零食(先拿到巧克力)
top_snack = snack_stack.pop()
print("拿出来的零食:", top_snack) # 输出:巧克力
print("剩下的零食:", snack_stack) # 输出:['薯片', '饼干']
# 查看栈顶(不拿走):最上面现在是饼干
print("现在最上面的零食是:", snack_stack[-1]) # 输出:饼干
# 看看栈里还有多少零食
print("零食数量:", len(snack_stack)) # 输出:2
# 判断栈是否为空(用if条件)
if snack_stack:
print("还有零食,继续吃")
else:
print("零食吃光了")
小提醒:只能从栈顶操作!如果你用snack_stack.pop(0)想拿底下的薯片,那就不是栈了(变成了队列)。而且列表的pop(0)效率很低,因为需要移动所有元素。
栈的实际应用:不止是理论知识
栈在编程中非常有用,GESP考试也常考。下面是两个经典应用:
1. 括号匹配(检查代码里的括号是否成对)
比如(1+2)*[3+{4-5}]是合法的,而(1+2]就不合法。原理就是:遇到左括号( [ {就压栈,遇到右括号就弹出栈顶并检查是否匹配。如果遍历完栈为空,则配对成功。
2. 浏览器后退模拟(用两个栈还能实现前进)
用一个栈存“已访问的页面”,后退时把当前页面弹出并存入另一个栈(前进栈)。代码如下:
# 模拟浏览器后退功能
history = [] # 历史栈,保存已访问的页面
current_page = None
def visit(url):
"""访问一个新页面"""
global current_page
if current_page:
history.append(current_page) # 把当前页面压入历史栈
current_page = url
print(f"正在访问:{current_page}")
def back():
"""后退到上一页"""
global current_page
if history:
current_page = history.pop() # 从栈顶弹出上一页
print(f"后退到:{current_page}")
else:
print("没有历史记录了!")
# 测试
visit("百度") # 当前:百度
visit("知乎") # 当前:知乎,历史栈:['百度']
visit("B站") # 当前:B站,历史栈:['百度', '知乎']
back() # 后退到:知乎
back() # 后退到:百度
back() # 没有历史记录了!
3. 函数调用栈(你每天都在用)
程序运行时,每次调用函数都会把一个“栈帧”压入调用栈,函数执行完再弹出。如果你在函数里调用自己(递归),栈就会越压越高,直到超出限制(RecursionError)。这就是为什么递归太深会报错。
新手最容易犯的3个错误
错误1:从栈中间或底部操作
stack = [1, 2, 3]
stack.insert(0, 0) # 把0插到最底下 —— 破坏了栈的规则!
stack.pop(0) # 弹出最底下的元素 —— 不是栈操作!
改正:只使用append和pop()(不带参数)操作栈顶。
错误2:出栈前不检查栈是否为空
stack = []
item = stack.pop() # 报错!IndexError: pop from empty list
改正:先判断if stack:,或者用try...except捕获错误。
错误3:混淆栈和队列
栈是后进先出(LIFO),队列是先进先出(FIFO)。比如排队打饭:先来的人先打到饭,这是队列,不能用栈模拟。
完整可运行示例:检查括号是否配对
下面是一个完整的程序,用来检查一段表达式里的括号是否都正确配对。你可以把代码复制到Python环境里运行测试。
def check_brackets(expression):
"""检查表达式中的括号是否匹配,返回True或False"""
stack = [] # 创建一个空栈
# 字典:左括号对应右括号
brackets = {'(': ')', '[': ']', '{': '}'}
for char in expression: # 遍历表达式中的每个字符
if char in brackets: # 如果是左括号,压栈
stack.append(char)
elif char in brackets.values(): # 如果是右括号
if not stack: # 栈为空,说明没有左括号与之配对
return False
left = stack.pop() # 弹出栈顶的左括号
# 检查弹出的左括号是否与当前右括号匹配
if brackets[left] != char:
return False # 不匹配,比如 [ 遇到 }
# 遍历完后,如果栈为空说明全部配对
return len(stack) == 0
# 测试几个例子
test_expressions = [
"(1+2)*[3+{4-5}]", # 正确
"{([])}", # 正确
"({[})", # 错误:} 和 [ 不匹配
"(()", # 错误:少一个右括号
")", # 错误:孤立右括号
]
for expr in test_expressions:
result = check_brackets(expr)
print(f"{expr:20} -> {'√配对正确' if result else '×配对错误'}")
运行结果:
(1+2)*[3+{4-5}] -> √配对正确
{([])} -> √配对正确
({[}) -> ×配对错误
(() -> ×配对错误
) -> ×配对错误
想继续学习?这些知识点和栈有关
- 队列(queue):用
collections.deque实现先进先出,适合模拟排队。 - 双端队列(deque):可以两端操作,比列表更适合做栈和队列。
- 递归函数:理解函数调用栈(call stack)能帮你搞懂递归原理。
- 深度优先搜索(DFS):图算法中常用栈实现(或递归本质也是栈)。
- 逆波兰表达式(后缀表达式):计算器如何计算
1 2 + 3 *?全靠栈!
栈虽然简单,但它是很多高级算法的基石。掌握了它,你就能像搭积木一样,用它解决各种实际问题。
例题精讲
使用Python列表模拟栈时,下列哪个方法是正确的压栈操作?
用Python列表模拟栈时,pop()方法默认删除并返回列表最后一个元素,对应栈的出栈操作。
以下代码使用列表模拟栈,实现了一个检查括号是否匹配的功能。请补全缺失的代码。
def is_balanced(s):
stack = []
mapping = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in mapping:
if not stack or stack.pop() != mapping[char]:
return False
else:
___
return not stack已知一个栈初始为空,依次执行以下操作:push(1), push(2), pop(), push(3), pop()。最终栈中的元素是?
使用Python列表的insert(0, x)方法模拟栈的压栈操作,其时间复杂度为O(1),与append相同。