栈是线性的集合,其中,访问都严格的限制在一端,也叫作顶(top)。栈是遵从后进先出(LIFO)的协议,从栈放入项和从栈删除项的操作分别叫压人(push)和弹出(pop)。
栈并不是Python的内建类型,Python程序员在必要的时候可以使用列表来模拟基于数组的栈。如果将列表的末尾看作是栈的顶,列表方法append就是将元素压入到栈中,而列表方法pop会删除并返回栈顶的元素。
栈的应用:
- 将中缀表达式转换为后缀表达式,并且计算后缀表达式
- 回溯算法
- 管理计算机内存以支持函数和方法调用
- 支持文本编辑器、字处理程序、电子表格程序、绘图程序或类似的应用程序中的撤销功能
- 维护Web浏览器所访问过的连接的历史记录
一、中缀表达式转换为后缀表达式
中缀表达式转后缀表达式的规则:
1.遇到操作数,直接输出;
2.栈为空时,遇到运算符,入栈;
3.遇到左括号,将其入栈;
4.遇到右括号,执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出;
5.遇到其他运算符’+”-”*”/’时,弹出所有优先级大于或等于该运算符的栈顶元素,然后将该运算符入栈;
6.最终将栈中的元素依次出栈,输出。
经过上面的步骤,得到的输出既是转换得到的后缀表达式。
def middle2behind(expresssion):
result = [] # 结果列表
stack = [] # 栈
for item in expression:
if item.isnumeric(): # 如果当前字符为数字那么直接放入结果列表
result.append(item)
else: # 如果当前字符为一切其他操作符
if len(stack) == 0: # 如果栈空,直接入栈
stack.append(item)
elif item in '*/(': # 如果当前字符为*/(,直接入栈
stack.append(item)
elif item == ')': # 如果右括号则全部弹出(碰到左括号停止)
t = stack.pop()
while t != '(':
result.append(t)
t = stack.pop()
# 如果当前字符为加减且栈顶为乘除,则开始弹出
elif item in '+-' and stack[len(stack)-1] in '*/':
if stack.count('(') == 0: # 如果有左括号,弹到左括号为止
while stack:
result.append(stack.pop())
else: # 如果没有左括号,弹出所有
t = stack.pop()
while t != '(':
result.append(t)
t = stack.pop()
stack.append('(')
stack.append(item) # 弹出操作完成后将‘+-’入栈
else:
stack.append(item)# 其余情况直接入栈(如当前字符为+,栈顶为+-)
# 表达式遍历完了,但是栈中还有操作符不满足弹出条件,把栈中的东西全部弹出
while stack:
result.append(stack.pop())
# 返回字符串
return "".join(result)
expression = "3+(6*7-2)+2*3"
print(middle2behind(expression))
二、后缀表达式计算
def postfix_calculate(s):
"""注意-号和/号的顺序问题"""
stack=[]
for x in s:
if x.isdigit(): #如果字符是数字
stack.append(x)
elif x == "+":
a = stack.pop()
b = stack.pop()
stack.append(int(a)+int(b))
elif x == "-":
a = stack.pop()
b = stack.pop()
stack.append(int(b)-int(a))
elif x == "*":
a = stack.pop()
b = stack.pop()
stack.append(int(a)*int(b))
elif x == "/":
a = stack.pop()
b = stack.pop()
stack.append(int(b)/int(a))
return stack[len(stack)-1]
三、回溯算法
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
主要有两种技术来实现回溯算法:一种是使用栈,还有一种是使用递归。下面介绍使用栈来实现回溯算法,在这个过程中,栈的作用是在每一个关头记住可替代的状态。
题目描述
定义一个二维数组N*M(其中2<=N<=10;2<=M<=10),如5 × 5数组下所示:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。入口点为[0,0],即第一空格是可以走的路。
Input
一个N × M的二维数组,表示一个迷宫。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。
Output
左上角到右下角的最短路径,格式如样例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
分析
元素--状态空间分析:每个坐标是是一个元素,每次可以在上一个坐标基础上向右[0,1]或者向下[1,0]移动一步。所以这里可以看出,元素是不固定的,状态空间固定。
maze = [
[0,1,0,0,0],
[0,1,1,1,0],
[0,1,0,0,0],
[0,1,0,1,0],
[0,0,0,1,0]
]
dirs = [
lambda x,y:(x+1,y),#下
lambda x,y:(x-1,y),#上
lambda x,y:(x,y-1),#左
lambda x,y:(x,y+1),#右
]
def maze_path(x1,y1,x2,y2):
stack = []
stack.append((x1,y1))
while(len(stack)>0):
biz=0
curNode=stack[-1] # 当前的节点
if curNode[0]==x2 and curNode[1]==y2:
# 走到终点了
for p in stack:
print(p)
return True
# x,y 四个方向:上 x-1,y, 右 x,y+1, 下 x+1,y, 左 x,y-1
for dir in dirs:
nextNode = dir(curNode[0],curNode[1])
if nextNode[0]>4 or nextNode[0]<0 or nextNode[1]<0 or nextNode[1]>4:
continue
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
maze[nextNode[0]][nextNode[1]] = 2 #避免走回头路
biz=1
break
if biz==0:
stack.pop()
print('没有路')
return False
maze_path(0,0,4,4)
以上三个关于栈的实例中都有采用列表模拟,但python中有专门的模块定义了栈:
栈的相关操作如下:
- Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。
- push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。
- pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。
- peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。
- isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。
- size() 返回栈中的 item 数量。不需要参数,并返回一个整数。
下表展示了栈的操作序列结果:
以下栈实现代码假定列表的结尾将保存栈的顶部元素。随着栈增长(push 操作),新项将被添加到列表的末尾。 pop 也操作列表末尾的元素。
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)
记住我们只定义类的实现,我们需要创建一个栈,然后使用它。以下代码展示了我们通过实例化 Stack 类执行 栈的操作。注意,Stack 类的定义是从 pythonds 模块导入的。
from pythonds.basic.stack import Stack
s=Stack()
print(s.isEmpty())
s.push(4)
s.push('dog')
print(s.peek())
s.push(True)
print(s.size())
print(s.isEmpty())
s.push(8.4)
print(s.pop())
print(s.pop())
print(s.size())
四、十进制转换为二进制
“除 2” 算法假定我们从大于 0 的整数开始。不断迭代的将十进制除以 2,并跟踪余数。第一个除以 2 的余数说明了这个值是偶数还是奇数。偶数有 0 的余数,记为 0。奇数有余数 1,记为 1.我们将得到的二进制构建为数字序列,第一个余数实际上是序列中的最后一个数字。见 Figure 5 , 我们再次看到了反转的属性,表示栈可能是解决这个问题的数据结构。
from pythonds.basic.stack import Stack
def divideBy2(decNumber):
remstack = Stack()
while decNumber!=0:
num=decNumber%2
remstack.push(num)
decNumber//=2
binString=''
while not remstack.isEmpty()!=0:
binString+=str(remstack.pop())
return binString
print(divideBy2(12))