本章介绍栈数据类型,包括栈的概念、如何用 Python 实现栈(数组、链表)、栈的实际使用(数据逆置、匹配问题、算数计算原理、函数调用等原理)以及回溯法的概念和使用,包括常见案例(全排列、子集问题、求和谜题以及著名的 N 皇后问题)。
1 栈的概念
栈 (stack):限制数据插入、删除操作只能在一端进行的特殊序列,遵循后进先出的原则(Last In First Out, LIFO)。
允许插入、删除的一端为栈顶(top),另一段为栈底(bottom)。
在栈顶插入元素称为入栈(push),删除元素称为出栈(pop)。
栈中元素个数称为栈的长度。
2 栈的抽象数据类型
对于栈的抽象数据类型 (stack ADT) ,它应该满足如下操作:
S.push(e) :将元素 e 从栈顶插入栈。
S.pop() :将栈顶的元素出栈,即删除头部元素,并返回元素的值。如果栈为空,则报错。
S.top() :返回栈顶元素的值。如果栈为空,则报错。
S.is_empty() :如果栈中无元素,则返回 True 。
len(S) :重载运算符 __len__() ,返回栈的元素个数。
以下是栈的抽象数据类型的一些操作示意:
3 基于数组实现栈
Python 提供的 list 类是数组的一种,它已经提供了 append() 和 pop() 方法,所以可以自然的利用 list 类来实现栈,只需要将列表类的尾部视作栈的顶部即可。但 list 类支持从列表中间插入元素,这违反了栈的规定。所以我们要利用 list 类重新定义一个新的栈类 ArrayStack 。
File name: example.txt Create time: 2025/3/13 Objective: for reverse Body: By using stack data structure, we read this file and push every lines into the stack. After that, pop the objects (lines) one by one. And that is the result --- a reversed file End!
经过逆置后
1
reverse_file('example.txt')
1 2 3 4 5 6 7 8 9 10 11 12 13
End! --- a reversed file And that is the result one by one. pop the objects (lines) After that, and push every lines into the stack. we read this file By using stack data structure, Body: Objective: for reverse Create time: 2025/3/13 File name: example.txt
S = ArrayStack() for c in expr: if c in lefty: # 左括号入栈 S.push(c) elif c in righty: # 右括号比较 if S.is_empty(): returnFalse# 如果右括号前没有栈元素,肯定不匹配 if righty.index(c) != lefty.index(S.pop()): returnFalse# 删去栈顶,查看栈顶左括号和现在右括号是否匹配 # 栈清空,则匹配成功 return S.is_empty()
<body> <center> <h1> The Little Boat </h1> </center> <p> The storm tossed the little boat like a cheap sneaker in an old washing machine. The three drunken fishermen were used to such treatment, of course, but not the tree salesman, who even as a stowaway now felt that he had overpaid for the voyage. </p> <ol> <li> Will the salesman die?</li> <li> What color is the boat?</li> <li> And what about Naomi?</li> </ol> </body>
defspans(X, n): """ 计算最长升序跨度 :param X: 原序列 :param n: 考察序列 X 的前 n 项 :return S 跨度列表 """ S = [] # 记录跨度 A = ArrayStack() # 栈
for i inrange(n): # 遍历 X 的每个元素 while (not A.is_empty()) and X[A.top()] <= X[i]: # 当栈非空并且栈顶元素仍然更小时,不断出栈,直到找到比当前元素更大的数 A.pop() if A.is_empty(): # 如果栈空,当前位置 i 前所有数都比当前数小,那跨度为 i + 1 S.append(i + 1) else: # 否则,跨度为当前位置 i 减去比自己大的数的位置 A.top() S.append(i - A.top()) # 记录目前最大值的位置 A.push(i) return S
defeval_exp(tokens): defprecedence(op): """为运算符定义优先级""" if op == '$': return -1# 结束符优先级最低 elif op in ('<', '>', '='): return0# 比较运算符 elif op in ('+', '-'): return1# 加减 elif op in ('*', '/'): return2# 乘除 else: raise ValueError(f"Invalid operator: {op}")
defsolveNQueens_stack(N): res = [] # 存储所有有效解 stack = ArrayStack() # 初始状态:空路径,第 0 行,第一个 set 表示列,第二个表示正对角线,第三个表示反对角线 stack.push(([], 0, set(), set(), set()))
whilenot stack.is_empty(): # Step 1: 出栈当前状态 path, row, cols, diag1, diag2 = stack.pop() # 如果所有行都处理完毕,说明 path 是一个有效解 if row == N: # 转换为棋盘表示 res.append([' · ' * i + ' Q ' + ' · ' * (N - i - 1) for i in path]) continue
# Step 2: 在当前行选择列的位置 for col inrange(N): # 计算当前列所在的正对角线和反对角线 curr_diag1 = row - col # 正对角线:行 - 列 curr_diag2 = row + col # 反对角线:行 + 列 # 如果当前列或对角线已经被占用,则跳过 if col in cols or curr_diag1 in diag1 or curr_diag2 in diag2: continue