什么是栈?
一种有次序的数据项集合,在栈中,数据 项的加入和移除都仅发生在同一端 这一端叫栈“顶top”,另一端叫栈“底base”
距离栈底越近的数据项,留在栈中的时间 就越长 而最新加入栈的数据项会被最先移除。这种次序通常称为“后进先出LIFO”: Last in First out 这是一种基于数据项保存时间的次序,时间越短 的离栈顶越近,而时间越长的离栈底越近。
栈的特性:反转次序
这种访问次序反转的特性,我们在某些计 算机操作上碰到过 浏览器的“后退back”按钮,最先back的是最 近访问的网页 Word的“Undo”按钮,最先撤销的是最近操作。
抽象数据类型 Stack
抽象数据类型“栈”定义为如下的操作
Stack():创建一个空栈,不包含任何数据项
push(item):将item加入栈顶,无返回值
pop():将栈顶数据项移除,并返回,栈被修改
peek():“窥视”栈顶数据项,返回栈顶的数 据项但不移除,栈不被修改
isEmpty():返回栈是否为空栈
size():返回栈中有多少个数据项
用Python实现ADT Stack
在清楚地定义了抽象数据类型Stack之后 ,我们看看如何用Python来实现它?
Python的面向对象机制,可以用来实现 用户自定义类型 将ADT Stack实现为Python的一个Class 将ADT Stack的操作实现为Class的方法 由于Stack是一个数据集,所以可以采用Python 的原生数据集来实现,我们选用最常用的数据集 List来实现。
一个细节:Stack的两端对应list设置 可以将List的任意一端(index=0或者-1)设置 为栈顶 我们选用List的末端(index=-1)作为栈顶 这样栈的操作就可以通过对list的append和pop 来实现,很简单!
# 用python实现ADT Stack Chenyj
class Stack:
def __init__(self):
self.items=[]
def isEmpty(self):
return self.items==[]
def push(self,item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
栈的应用:简单括号匹配
从左到右扫描括号串,最新打开的左括号,应该 匹配最先遇到的右括号 这样,第一个左括号(最早打开),就应该匹配 最后一个右括号(最后遇到) 这种次序反转的识别,正好符合栈的特性!
# 括号匹配 Chenyj
from pythonds.basic.stack import Stack
def parChecker(symbolString):
s=Stack()
balanced=True
index=0
while index< len(symbolString) and balanced:
symbol=symbolString[index]
if symbol=='(':
s.push(symbol)
else:
if s.isEmpty():
balanced=False
else:
s.pop()
index =index+1
if balanced and s.isEmpty():
return True
else:
return False
print(parChecker('((()))'))
print(parChecker('(()'))
运行结果
当然还可以举一反三 {} [] 都可以匹配
栈的应用:十进制转换为二进制
十进制转换为二进制,采用的是“除以2 求余数”的算法 将整数不断除以2,每次得到的余数就是由低到 高的二进制位。
“除以2”的过程,得到的余数是从低到 高的次序,而输出则是从高到低,所以需 要一个栈来反转次序。
# 括号匹配 Chenyj
from pythonds.basic.stack import Stack
def divideBy2(decNumber):
remstack =Stack()
while decNumber>0:
rem=decNumber%2
remstack.push(rem)
decNumber=decNumber//2
binString=''
while not remstack.isEmpty():
binString=binString+str(remstack.pop())
return binString
print(divideBy2(42))
运行结果: 101010
十进制转换为二进制的算法,很容易可以 扩展为转换到任意N进制 只需要将“除以2求余数”算法改为“除以N求余 数”算法就可以。
栈的应用:表达式转换
中缀表达式
我们通常看到的表达式象这样:B*C,很 容易知道这是B乘以C
这种操作符(operator)介于操作数( operand)中间的表示法,称为“中缀” 表示法
例如中缀表达式A+B 将操作符移到前面,变为“+AB” 或者将操作符移到最后,变为“AB+”。
我们就得到了表达式的另外两种表示法: “前缀”和“后缀”表示法 以操作符相对于操作数的位置来定义
无论表达式多复杂,需要转换成 前缀或者后缀,只需要两个步骤 将中缀表达式转换为全括号形式 将所有的操作符移动到子表达式所在的左括号( 前缀)或者右括号(后缀)处,替代之,再删除 所有的括号
通用的中缀转后缀算法:实例
# 用python实现ADT Stack Chenyj
from pythonds.basic.stack import Stack
def infixToPostfix(infixexpr):
prec={}
prec['*'] = 3
prec['/'] = 3
prec['+'] = 3
prec['-'] = 3
prec['('] = 3
opStack =Stack()
postfixList=[]
tokenList=infixexpr.split()
for token in tokenList:
if token in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' or token in '0123456789':
postfixList.append(token)
elif token=='(':
opStack.push(token)
elif token==')':
topToken =opStack.pop()
while topToken !='(':
postfixList.append(topToken)
topToken=opStack.pop()
else:
while (not opStack.isEmpty()) and \
(prec[opStack.peek()]>=prec[token]):
postfixList.append(opStack.pop())
opStack.push(token)
while not opStack.isEmpty():
postfixList.append(opStack.pop())
return ' '.join(postfixList)
栈的应用:后缀表达式求值
作为栈结构的结束,我们来讨论“后缀表 达式求值”问题 ,跟中缀转换为后缀问题不同, 在对后缀表达式从左到右扫描的过程中, 由于操作符在操作数的后面, 所以要暂存操作数,在碰到操作符的时候 ,再将暂存的两个操作数进行实际的计算 仍然是栈的特性:操作符只作用于离它最近的两 个操作数。
后缀表达式求值:流程
创建空栈operandStack用于暂存操作数
将后缀表达式用split方法解析为单词( token)的列表
从左到右扫描单词列表:如果单词是一个操作数,将单词转换为整数int,压 入operandStack栈顶 如果单词是一个操作符(*/+-),就开始求值,从栈 顶弹出2个操作数,先弹出的是右操作数,后弹出的 是左操作数,计算后将值重新压入栈顶
单词列表扫描结束后,表达式的值就在栈顶
弹出栈顶的值,返回。
# 用python实现ADT Stack Chenyj
from pythonds import Stack
def postfixEval(postfixExpr):
operandStack = Stack()
tokenList = postfixExpr.split()
for token in tokenList:
if token.isdigit(): # 使用 isdigit() 检查是否为数字
operandStack.push(int(token))
else:
operand2 = operandStack.pop()
operand1 = operandStack.pop()
result = doMath(token, operand1, operand2)
operandStack.push(result)
return operandStack.pop()
def doMath(op, op1, op2):
if op == '*':
return op1 * op2
elif op == '/':
return op1 / op2
elif op == '+':
return op1 + op2
elif op == '-':
return op1 - op2
else:
raise ValueError(f"Invalid operator: {op}") # 添加无效操作符的处理
# 示例
if __name__ == "__main__":
expression = "3 4 + 2 * 7 /" # 示例后缀表达式
result = postfixEval(expression)
print(result) # 输出结果