栈、队列、双端队列、列表都是有序的数据集合,其元素的顺序取决于添加顺序或移除顺序。若某个元素被添加进来,其与前后元素的相对位置将保持不变,这样的数据集合通常被称为线性数据结构。
栈也称”下堆栈“,是一种有序集合,它的添加操作和移除操作总发生在同一端,即”顶端“,另一端则为”底端“,元素离底端越近,其在栈中存在的时间则越长。
特性
排序原则——LIFO(last-in-first-out),即后进先出。
实例
厨房里堆放的盘子、浏览器的多层网页(URL被存在栈中,点击返回按钮时可以反向浏览网页)。
应用
反转特性:可以用来将元素反向重排。
如前所述,栈的操作顺序时LIFO,它支持以下操作。
Stack()
创建一个空栈。无参数,返回一个空栈。push(item)
添加一个元素到栈的顶端。参数item,无返回值。pop()
移除栈顶端元素。无参数,返回顶端元素。peek()
得到栈顶端元素(不移除)。无参数,返回顶端元素。isEmpty()
检查栈是否为空。无参数,返回布尔值。size()
得到栈中元素数目。无参数,返回整数。 在Python中实现抽象数据类型时,就可以创建新类,其操作通过方法实现。
使用Python提供的简单、强大的原生集合——列表来实现栈(假设列表的尾部是栈的顶端):
#代码3-1 Python实现栈
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)
另一种实现方式(假设列表的头作为栈的顶端):
#代码3-2 Python实现栈
class Stack:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self, item):
self.items.insert(0, item)
def pop(self):
return self.items.pop(0)
def peek(self):
return self.items[0]
def size(self):
return len(self.items)
这种实现方式中不能直接用append
和pop
方法,而是用insert
和pop
方法显式地访问下标为0的元素。
虽然两种方法实现的功能相同,都能完成栈支持的方法,但在性能方面有差异:append()
和pop()
方法的时间复杂度都是O(1);而insert(0)
和pop(0)
方法的时间复杂度都是O(n)。
括号匹配指每一个左括号都有与之对应的一个右括号,并且括号对有正确的嵌套关系。
正确匹配的括号串:
(()()()())
(((())))
(()(()()))
不匹配的括号串:
(((((())
())))
())()()
用栈完成括号匹配算法:
思想
:从一个空栈开始,依次扫描括号串,遇到左括号则push()
压入栈中,遇到右括号则pop()
从栈中取出;如果处理完匹配的括号串之后栈未空,则不匹配,反之则匹配。
#代码3-3 匹配括号
from pythonds.basic import Stack
def parChecker(str):
s = Stack()
balanced = True
index = 0
while index < len(str) and balanced:
symbol = str[index]
if symbol == "(":
s.push(symbol)
else:
if s.isEmpty():
balanced = False
else:
s.pop()
index = index + 1
if balanced and s.isEmpty():
return True
else:
return False
将上述情况扩展到中括号、花括号等情况下(增加一个匹配方法):
#代码3-4 匹配符号
from pythonds.basic import Stack
def parChecker(symStr):
s = Stack()
balanced = True
index = 0
while index < len(symStr) and balanced:
symbol = symStr[index]
if symbol in "([{":
s.push(symbol)
else:
if s.isEmpty():
balanced = False
else:
top = s.pop()
if not matches(top, symbol):
balanced = False
index = index + 1
if balanced and s.isEmpty():
return True
else:
return False
def matches(left, right):
lefts = "([{"
rights = ")]}"
return lefts.index(left) == rights.index(right)
将一个十进制数不停除以2,余数记录下来,反过来就是其二进制的数值,因此可以用栈来解决该问题。例如将十进制数(233)10转换为二进制数(11101001)2的过程:
将十进制数转换成任意进制数(基数小于等于16):
#代码3-5 转换进制
from pythonds.basic import Stack
def baseConvert(num, base):
digits = "0123456789ABCDEF"
remstack = Stack()
while num > 0:
rem = num % base
remstack.push(rem)
num = num // base
newStr = ""
while not remstack.isEmpty():
newStr = newStr + digits[remstack.pop()]
return newStr
中序表达式 | 前序表达式 | 后序表达式 |
---|---|---|
A+B | +AB | AB+ |
A+B*C | +A*BC | ABC*+ |
(A+B)*(C+D) | *+AB +CD | AB+CD+* |
由于前序和后序表达式中的运算符所对应的操作数是明确的,表达式的运算顺序完全由运动算符的位置来决定,只有中序表达式需要额外的括号来消除歧义。因此,中序表达式是最不理想的算式表达法。
若要将任意复杂的中序表达式转换成前序或后序表达式,只需先将中序表达式写作完全括号表达式,然后将括号内的运算符移到左括号处(前序表达式)或者右括号处(后序表达式)。
从左往右扫描中序表达式时,用栈来保存运算符。这样可以提供反转特性,让中序表达式中先低级后高级的运算变成先高级运算、后低级运算。每当遇到一个新的运算符时,都需要对比其与栈中运算符的优先级:“*”,“/”为最高级,“+”,“-”次之,“(”左括号的优先级最低。
假设中序表达式中的运算符标记有*、/、+和-,括号标记符有(和),操作数标记有A、B、C等。下面的步骤会生成一个后序标记串。
split
将输入的中序表达式转换成一个列表。 在Python中实现这一算法:
#代码3-6 中序表达式转换后序表达式
from pythonds.basic import Stack
import string
def midTopost(midExpr):
prec = {}
prec["*"] = 3
prec["/"] = 3
prec["+"] = 2
prec["-"] = 2
prec["("] = 1
opStack = Stack()
postExprList = []
midExprList = midExpr.split()
for ch in midExprList:
if ch in string.ascii_uppercase:
postExprList.append(ch)
elif ch == "(":
opStack.push(ch)
elif ch == ")":
topch = opStack.pop()
while topch != "(":
postExprList.append(topch)
topch = opStack.pop()
else:
while (not opStack.isEmpty()) and \
(prec[opStack.peek()] >= prec[ch]):
postExprList.append(opStack.pop())
opStack.push(ch)
while not opStack.isEmpty():
postExprList.append(opStack.pop())
return "".join(postExprList)
上述操作将中序表达式转换成后序表达式后,接下来要考虑的就是如何计算后序表达式。同样是利用栈的反转特性完成算法,不过当扫描后序表达式时,需要保存操作数,而不是运算符。
每当遇到运算符时,意味着需要将最近遇到的两个操作数相乘。通过执行两次出栈操作,可以得到相应的操作数,然后进行运算后将运算结果再压入栈中,作为后续运算的操作数。当处理完最后一个运算符后,栈中只剩下一个值,将其取出并作为表达式运算的返回值。
假设后序表达式中的运算符标记有*、/、+和-,操作数标记是一位的整数值。下面的步骤会计算结果,并返回一个整数值。
split
将输入的后序表达式转换成一个列表。在Python中实现这一算法:
#代码3-7 计算后序表达式
from pythonds.basic import Stack
def postExprCal(postExpr):
opStack = Stack()
postExprList = postExpr.split()
for ch in postExprList:
if ch in "0123456789":
opStack.push(int(ch))
else:
op2 = opStack.pop()
op1 = opStack.pop()
result = doMath(ch, op1, op2)
opStack.push(result)
return opStack.pop()
def doMath(op, op1, op2):
if op =="*":
return op1*op2
elif op == "/":
return op1/op2
elif op == "+":
return op1+op2
elif op == "-":
return op1-op2
待续。