栈的抽象数据类型
栈的抽象数据类型由以下结构和操作定义:栈被构造为项的有序集合,其中项被添加和从末端移除的位置称为“顶部”。栈是有序的 LIFO 。栈操作如下:
- Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。
- push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。
- pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。
- peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。
- isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。
- size() 返回栈中的 item 数量。不需要参数,并返回一个整数。
简单来说,类似于放一摞书,最先放的书在最下面,因此最后放的书最先取出来,而最后取出放的第一本书,栈就是这样一种数据结构。我们可以用Python的列表 (List) 定义一个栈类型:
class Stack():
def __init__(self):
self.item = []
def isEmpty(self):
return self.item == []
def push(self, item):
self.item.append(item)
def pop(self):
return self.item.pop()
def peek(self):
return self.item[-1]
def size(self):
return len(self.item)
栈有什么用呢?以下一个经典的例子用到了栈的特性:
简单括号匹配
何为括号匹配?看简单的几个例子:
( ) ( ) ( ( ( ) ) ) ( ) # 该括号组合是匹配的
) ( ) ( ) # 该括号组合是不匹配的
区分括号是否匹配的能力是识别很多编程语言结构的重要部分。具有挑战的是如何编写一个算法,能够从左到右读取一串符号,并决定括号是否匹配。为了解决这个问题,我们需要做一个重要的观察。从左到右处理符号时,最近的左括号 (" ( “) 必须与下一个右括号 (” ) ") 相匹配。此外,处理的第一个左括号必须等待直到其匹配最后一个右括号。右括号以相反的顺序匹配左括号,他们从内到外匹配。这是一个可以用栈解决问题的线索。
因此实现这个算法的Python代码思路很清晰:
- 创建一个空栈
- 读取括号列表,第一个括号若以左括号开始,将其压入栈中,否则直接返回False(不匹配)
- 判断下一个括号类型,左括号继续压入栈中,若是右括号则删除栈顶部左括号。
- 当列表遍历完时,栈正好被清空,则括号匹配,返回True;其他情况返回False。
算法实现的代码如下:
def parChecker(symbolString):
s = Stack() # 初始化定义的栈对象
balance = True # 初始化匹配状态值
i = 0
while i < len(symbolString):
symbol = symbolString[i]
if symbol == '(':
s.push(symbol) # 指针指到左括号便将其压入栈
else:
if s.isEmpty(): # 指针指向右括号时栈为空,即没有与之匹配的左括号,因此整体不匹配,返回False
balance == False
break
else:
s.pop() # 指针指向右括号时栈不为空,故删除栈顶与之匹配的左括号
i += 1 # 指针右移一个单位
return True if balance and s.isEmpty() else False
拓展:上面显示的匹配括号问题是许多编程语言都会出现的一般情况的特定情况。匹配和嵌套不同种类的开始和结束符号的情况经常发生。例如,在 Python 中,方括号 [ ] 用于列表,花括号 { } 用于字典。括号 ( ) 用于元祖和算术表达式。只要每个符号都能保持自己的开始和结束关系,就可以混合符号,甚至指定任意匹配方式的字符串进行匹配。符号字符串如
# 该符号组合是匹配的
( ) { } [ ]
( { [ ] } )
这些被恰当的匹配了,因为不仅每个开始符号都有对应的结束符号,而且符号的类型也匹配。相反这些字符串没法匹配:
# 该符号组合不是匹配的
( ) { } [ ] }
( { [ ] }
可以想到,处理多符号匹配的问题和简单括号匹配的思路和算法一样,只不过需要记录各符号各自的匹配关系。不难想到,在Python中我们可以用字典 (dict) 的键值对储存各个符号的匹配关系,以便调用。Python实现代码如下:
# 匹配 {}[]() 三种字符
def parChecker(symbolString):
s = Stack() # 初始化定义的栈对象
balance = True # 初始化匹配状态值
dict_symbol = {'{':'}', '[':']', '(':')'}
i = 0
while i < len(symbolString):
symbol = symbolString[i]
if symbol in '({[':
s.push(symbol) # 指针指到左符号便将其压入栈
else:
if s.isEmpty() or dict_symbol[s.peek()] != symbol: # 指针指向右符号时栈为空,或是栈顶部符号与之不匹配,故整体不匹配,返回False
balance == False
break
else:
s.pop() # 指针指向右符号时栈顶部符号与之匹配,故删除栈顶与之匹配的符号
i += 1 # 指针右移一个单位
return True if balance and s.isEmpty() else False