数据结构Python与栈的爱恨情仇

Posted ZSYL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构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. 栈的列表实现

列表的栈操作:

  1. 生成链表
  2. 入栈
  3. 出栈
  4. 返回栈顶元素
  5. 判断是否为空栈
  6. 返回栈内元素个数

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类实现,当我们想要在实现在容器两端更快速地进行appendpop操作时,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 中缀表达式转换为后缀表达式

中缀表达式转后缀表达式的规则

  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))
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))

参考Link Link Link


加油!

感谢!

努力!

以上是关于数据结构Python与栈的爱恨情仇的主要内容,如果未能解决你的问题,请参考以下文章

Python数据科学快速入门系列 | 04Numpy四则运算矩阵运算和广播机制的爱恨情仇

python 第一天之高级语言与低级语言的爱恨情仇

Python趣味入门7:循环与条件的爱恨情仇

深度学习 与 C++ 的爱恨情仇

对json的爱恨情仇

继承的爱恨情仇——一场钻石引发的血案