数据结构Python与栈的爱恨情仇
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构Python与栈的爱恨情仇相关的知识,希望对你有一定的参考价值。
【数据结构】Python与栈的爱恨情仇
1. 栈的概念
栈是线性的集合,其中,访问都严格的限制在一端,也叫作顶(top)。
栈是遵从后进先出(LIFO)的协议,从栈放入项和从栈删除项的操作分别叫压人(push)和弹出(pop)。
栈并不是Python的内建类型,Python程序员在必要的时候可以使用列表来模拟基于数组的栈。如果将列表的末尾看作是栈的顶,列表方法append就是将元素压入到栈中,而列表方法pop会删除并返回栈顶的元素。
栈是一种线性数据结构,用先进后出或者是后进先出的方式存储数据,栈中数据的插入删除操作都是在栈顶端进行,常见栈的函数操作包括:
- empty() – 返回栈是否为空 – Time Complexity : O(1)
- size() – 返回栈的长度 – Time Complexity : O(1)
- top() – 查看栈顶元素 – Time Complexity : O(1)
- push(g) – 向栈顶添加元素 – Time Complexity : O(1)
- pop() – 删除栈顶元素 – Time Complexity : O(1)
2. 栈的列表实现
列表的栈操作:
- 生成链表
- 入栈
- 出栈
- 返回栈顶元素
- 判断是否为空栈
- 返回栈内元素个数
Python中栈的操作函数(本例使用的函数):
- append() : 尾部添加元素
- pop():弹出尾部元素
代码示例:
class Stack(): #定义类
def __init__(self): #产生一个空的容器
self.__list = []
def push(self, item): #入栈
self.__list.append(item)
def pop(self): #出栈
return self.__list.pop()
def top(self): #返回栈顶元素
return self.__list[-1]
def isEmpty(self): #判断是否已为空
return not self.__list
def size(self): #返回栈中元素个数
return len(self.__list)
代码测试:
if __name__ == '__main__':
s = Stack()
c = 1
s.push('a')
s.push('b')
s.push(c)
print('size:' + str(s.size()))
print('speek:' + str(s.top()))
print(s.pop())
print(s.pop())
print(s.pop())
print('size:' + str(s.size()))
结果显示:
size:3
top:1
1
b
a
size:0
3. 栈的简洁实现
Python中栈可以用以下三种方法实现:
1)list
2)collections.deque
3)queue.LifoQueue
3.1 list
python的内置数据结构list可以用来实现栈,用append()向栈顶添加元素, pop() 可以以后进先出的顺序删除元素.
但是列表本身有一些缺点,主要问题就是当列表不断扩大的时候会遇到速度瓶颈.列表是动态数组,因此往其中添加新元素而没有空间保存新的元素时,它会自动重新分配内存块,并将原来的内存中的值复制到新的内存块中.这就导致了一些append()操作会消耗更多的时间。
>>> stack = []
>>> #append() fuction to push
... #element in list
...
>>> stack.append('hello')
>>> stack.append('world')
>>> stack.append('!')
>>> print('Initial stack')
Initial stack
>>> print(stack)
['hello', 'world', '!']
>>> #pop() function to pop element
... #from stack in LIFO order
...
>>> print('\\nElement poped from stack')
Element poped from stack
>>> print(stack.pop())
!
>>> print(stack.pop())
world
>>> print(stack.pop())
hello
>>> print('\\nStack after all elements are poped')
Stack after all elements are poped
>>> print(stack)
[]
3.2 collections.deque
python中栈也可以用deque类实现,当我们想要在实现在容器两端更快速地进行append和pop操作时,deque比列表更合适.deque可以提供O(1)时间的append和pop操作,而列表则需要O(n)时间。
>>> from collections import deque
>>> stack = deque()
>>> # append() fuction to push
... #element in list
...
>>> stack.append('hello')
>>> stack.append('world')
>>> stack.append('!')
>>> print('Initial stack')
Initial stack
>>> print(stack)
deque(['hello', 'world', '!'])
>>> #pop() function to pop element
... #from stack in LIFO order
...
>>> print('\\nElement poped from stack')
Element poped from stack
>>> print(stack.pop())
!
>>> print(stack.pop())
world
>>> print(stack.pop())
hello
>>> print('\\nStack after all elements are poped')
Stack after all elements are poped
>>> print(stack)
deque([])
3.3 queue module
Queue模块有LIFO queue
,也就是栈结构.用put()和get()操作从Queue中添加和获得数据。
>>> from queue import LifoQueue
>>> stack = LifoQueue(maxsize = 3)
>>> print(stack.qsize())
0
>>> stack.put('hello')
>>> stack.put('world')
>>> stack.put('!')
>>> print('\\nElement poped from stack')
Element poped from stack
>>> print(stack.get())
!
>>> print(stack.get())
world
>>> print(stack.get())
hello
>>> print('\\nEmpty:', stack.empty())
Empty: True
3.4 pythonds.basic.stack
记住我们只定义类的实现,我们需要创建一个栈,然后使用它。以下代码展示了我们通过实例化 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())
4. 栈的应用
- 将中缀表达式转换为后缀表达式,并且计算后缀表达式
- 回溯算法
- 管理计算机内存以支持函数和方法调用
- 支持文本编辑器、字处理程序、电子表格程序、绘图程序或类似的应用程序中的撤销功能
- 维护Web浏览器所访问过的连接的历史记录
4.1 中缀表达式转换为后缀表达式
中缀表达式转后缀表达式的规则:
- 遇到操作数,直接输出;
- 栈为空时,遇到运算符,入栈;
- 遇到左括号,将其入栈;
- 遇到右括号,执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出;
- 遇到其他运算符’+”-”*”/’时,弹出所有优先级大于或等于该运算符的栈顶元素,然后将该运算符入栈;
- 最终将栈中的元素依次出栈,输出。
经过上面的步骤,得到的输出既是转换得到的后缀表达式。
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))
367*2-23*++
4.2 后缀表达式计算
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]
expression = "3+(6*7-2)+2*3"
print(middle2behind(expression))
print(postfix_calculate(middle2behind(expression)))
367*2-23*++
49
4.3 回溯算法
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
主要有两种技术来实现回溯算法:一种是使用栈,还有一种是使用递归。下面介绍使用栈来实现回溯算法,在这个过程中,栈的作用是在每一个关头记住可替代的状态。
题目描述
定义一个二维数组N*M(其中2<=N<=10;2<=M<=10),
如5 × 5数组下所示:
int maze[5][5] = undefined
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)
4.4 十进制转换为二进制
“除 2” 算法假定我们从大于 0 的整数开始。不断迭代的将十进制除以 2,并跟踪余数。第一个除以 2 的余数说明了这个值是偶数还是奇数。偶数有 0 的余数,记为 0。奇数有余数 1,记为 1.我们将得到的二进制构建为数字序列,第一个余数实际上是序列中的最后一个数字。我们再次看到了反转的属性,表示栈可能是解决这个问题的数据结构。
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))
加油!
感谢!
努力!
以上是关于数据结构Python与栈的爱恨情仇的主要内容,如果未能解决你的问题,请参考以下文章