栈
栈是一个线性的有序集合,其中添加移除新项总发生在同一端。这一端通常称为“顶部”。与顶部对应的端称为“底部”。
最近添加的项是最先会被移除的。这种排序原则有时被称为LIFO(Last In First Out),后进先出。它基于在集合内的时间长度做排序。较新的项靠近顶部,较旧的项靠近底部。
栈的底部很重要,因为在栈中靠近底部的项是存储时间最长的。
栈的例子很常见。几乎所有的自助餐厅都有一堆托盘或盘子,你从顶部拿一个,就会有一个新的托盘给下一个客人。想象桌上有一堆书, 只有顶部的那本书封面可见,要看到其他书的封面,只有先移除他们上面的书。
下图展示了另一个栈,包含了很多 Python 对象。
和栈相关的最有用的想法之一来自对它的观察。假设从一个干净的桌面开始,现在把书一本本叠起来,你在构造一个栈。考虑下移除一本书会发生什么。移除的顺序跟刚刚被放置的顺序相反。栈之所以重要是因为它能反转项的顺序。插入跟删除顺序相反,下图展示了 Python 数据对象创建和删除的过程,注意观察他们的顺序。
想想这种反转的属性,你可以想到使用计算机的时候所碰到的例子。例如,每个 web 浏览器都有一个返回按钮。当你浏览网页时,这些网页被放置在一个栈中(实际是网页的网址)。你现在查看的网页在顶部,你第一个查看的网页在底部。如果按‘返回’按钮,将按相反的顺序浏览刚才的页面。
栈的实现
class Stack:
def __init__(self):
self.stack = []
def isempty(self):
if len(self.stack) == 0:
return True
else:
return False
def push(self, item):
self.stack.append(item)
def pop(self):
pop_num = self.stack.pop()
return pop_num
def peek(self):
peek_value = self.stack[-1]
return peek_value
def size(self):
return len(self.stack)
例1. 括号匹配问题
括号匹配意味着每个开始符号具有相应的结束符号,并且括号能被正确嵌套。考虑下面正确匹配的括号字符串:
(()()()()) (((()))) (()((())()))
对比那些不匹配的括号:
((((((()) ())) (()()(()
区分括号是否匹配的能力是识别很多编程语言结构的重要部分。具有挑战的是如何编写一个算法,能够从左到右读取一串符号,并决定括号是否平衡。
为了解决这个问题,我们需要以下的重要观察结果。
- 从左到右处理符号时,最近开始符号必须与下一个关闭符号相匹配。
- 处理的第一个开始符号必须等待直到其匹配最后一个符号。结束符号以相反的顺序匹配开始符号。他们从内到外匹配。
栈就是解决这个问题很恰当的数据结构,算法是很直接的。
- 从空栈开始,从左到右处理括号字符串。
- 如果一个符号是一个开始符号,将其作为一个信号,对应的结束符号稍后会出现。
- 如果符号是结束符号,则弹出栈,只要弹出栈的开始符号可以匹配结束符号,则括号保持匹配状态。如果任何时候栈上没有出现符合开始符号的结束符号,则字符串不匹配。
- 当所有符号都被处理后,栈应该是空的,如果非空,也表明括号是不匹配的。
代码实现:
s = '(()'
stack = Stack()
for items in s:
if items == '(':
stack.push(items)
else:
stack.pop()
a = stack.size()
if stack.size() == 0:
print(True)
else:
print(False)
例2. 进制间的相互转换
以10进制转2进制为例
“除 2” 算法假定我们从大于 0 的整数开始。不断迭代的将十进制除以 2,并跟踪余数。第一个除以 2 的余数说明了这个值是偶数还是奇数。偶数有 0 的余数,记为 0。奇数有余数 1,记为 1.我们将得到的二进制构建为数字序列,第一个余数实际上是序列中的最后一个数字。见下图, 我们再次看到了反转的属性,表示栈可能是解决这个问题的数据结构。
代码实现:
stack = Stack()
num = 16
while 1:
res = num % 2
stack.push(res)
num = num / 2
if num == 0:
break
bin = ''
for i in range(stack.size()):
bin += str(stack.pop())
print(bin)