线性结构Linear Structure
- 线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继。
- 线性结构总有两端,在不同情况下称呼不一样(前后、左右、顶底)。
- 两端的称呼并不是关键,不同线性结构的两端区别在于数据增减的方式。有的结构只允许数据项从一端添加,有的结构则允许 数据项从两端移除。
- 四个线性结构:
栈 stack —— 后进先出(LIFO:last in first out)
队列 queue —— 先进先出(FIFO:first in first out)
双端队列 deque
列表 list - 这些数据结构的共同点在于,数据项之间只存在先后的次序关系,都是线性结构。
栈Stack
- 一种有次序的数据项集合,在栈中数据的加入和移除都仅发生在同一端,这一端叫栈“顶top”,另一端叫栈“底base”。
- 距离栈底越近的数据项,留在栈中的时间就越长,而最新加入栈的数据项会被最先移除,这种次序通常称为“后进先出LIFO”。(last in first out)
- 栈的特性:反转次序,进栈和出栈的次序正好相反。如计算机网页访问、Word里的撤销按钮。
抽象数据类型Stack(ADT Stack)
-
抽象数据类型“栈”是一个有次序的数据集,每个数据项仅从“栈顶”一端加入到数据集中、从数据集中移除,栈具有后进先出LIFO的特性
-
抽象数据类型“栈”定义为如下的操作
Stack():创建一个空栈,不包含任何数据项
push(item):将item加入栈顶,无返回值
pop():将栈顶数据项移除,并返回,栈被修改
peek():“窥视”栈顶数据项,返回栈顶的数
据项但不移除,栈不被修改
isEmpty():返回栈是否为空栈
size():返回栈中有多少个数据项
ADT Stack操作样例
用列表实现ADT Stack
- list左端还是右端作为栈顶,性能有所区别。栈顶为左端时push/pop的复杂度为O(n),而栈顶为右端的实现push/pop的复杂度为O(1)。
栈的应用:简单括号匹配
-
LISP语言大量使用括号。
-
括号的使用遵循“平衡”规则,对括号的正确匹配是很多语言编译器的基础算法。
括号匹配代码
from pythonds.basic.stack import Stack
def parChecker(symbolString):
s = Stack()
balanced = True
index = 0
while index < len(symbolString) and balanced:
symbol = symbolString[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
print(parChecker('((()))'))
print(parChecker('(()'))
栈的应用:通用括号匹配
括号匹配代码
def matches(open,close):
opens = '{[('
closes = '}])' # 一样的括号位置要一样 index识别
return opens.index(open) == closes.index(close)
def parChecker(symbolString):
s = Stack()
balanced = True
index = 0
while index < len(symbolString) and balanced:
symbol = symbolString[index]
if symbol in '{[(': # 只判断三种括号
s.push(symbol)
else:
if s.isEmpty():
balanced = False
else:
top = s.pop()
if not matches(top,symbol): #top是open符号,symbol是close符号
balanced = False
index += 1
if s.isEmpty() and balanced:
return True
else:
return False
栈的应用:进制转换
- 十进制转二进制是“除以二”的过程,得到的余数是从低到高的次序,而输出则是从高到低,所以需要一个栈来次序反转。(最后开始读)
十进制转十六以下任意进制代码
from pythonds.basic.stack import Stack
def baseConverter(decNumber,base):
digits = "0123456789ABCDEF"
remstack = Stack()
while decNumber > 0:
rem = decNumber % base (# base看是转n进制,base=n)
remstack.push(rem)
decNumber = decNumber // base
newString = ""
while not remstack.isEmpty():
newString = newString + digits[remstack.pop()]
return newString
print(baseConverter(25,2))
print(baseConverter(25,16))
表达式转换
中缀表达法
- 中缀表达式形式:第一操作数、操作符、第二操作数。
- 中缀(infix)表达式:A + B * C + D ,为了避免计算顺序混淆,引入全括号表达式;
- 全括号中缀表达式:((A + (B * C)) + D)
前缀和后缀表达式
- 前缀(prefix)表达式:将操作符移到前面,形式变为:操作符、第一操作数、第二操作数。
- 后缀(postfix)表达式:将操作符移到后面,形式变为:第一操作数、第二操作数、操作符。
中缀转换为前缀/后缀表达式
例:(A + (B * C))
- 把操作符移到子表达式(B * C)的右括号位置替代它,再删去左括号得到BC,接着把操作符+移到相应的右括号并删掉左括号,表达式就转为 后缀 形式,即:ABC*+ 。
- 同理,把操作符移到相应的左括号替代之,并删掉右括号,表达时就转换为 前缀 形式,即:+A*BC 。
总结:无论表达式多复杂,转换为前缀或后缀只需要两个步骤:
(1) 将中缀表达式转换为全括号形式;
(2) 将所有操作符移动到子表达式所在的左括号(前缀)或者右括号(后缀)处,替代之,再删除所有括号。
通用的中缀转后缀算法
流程:
- 从左到右扫描中缀表达式单词列表,如果单词是操作数(a,b,c,1,2,3等),则直接添加到后缀表达式列表的末尾;
- 如果单词是左括号“(”,则压入opstack栈顶;
- 如果单词是右括号“)”,则反复弹出opstack栈顶操作符,加入到输出列表末尾,直到碰到左括号;
- 如果单词是操作符“*/±”,则压入opstack栈顶。但在压入栈顶之前,要比较其与栈顶操作符的优先级,如果栈顶的高于或等于它,就要反复弹出栈顶操作符,加入到输出列表末尾;直到栈顶的操作符优先级低于它。
- 中缀表达式单词列表扫描结束后,把opstack栈中的所有剩余操作符依次弹出,添加到输出列表末尾。
- 把输出列表再用join方法合并成后缀表达式字符串,算法结束。
代码实现:
from pythonds.basic.stack import Stack
def infixToPostfix(infixexpr):
prec = {
}
prec["*"] = 3 # 记录操作符优先级
prec["/"] = 3
prec["+"] = 2
prec["-"] = 2
prec["("] = 1
opStack = Stack()
postfixList = []
tokenList = infixexpr.split() # 解析到单词列表
for token in tokenList:
if token in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" or token in "0123456789":
postfixList.append(token)
elif token == '(':
opStack.push(token)
elif token == ')':
topToken = opStack.pop()
while topToken != '(':
postfixList.append(topToken)
topToken = opStack.pop()
else:
while (not opStack.isEmpty()) and \
(prec[opStack.peek()] >= prec[token]):
postfixList.append(opStack.pop())
opStack.push(token)
while not opStack.isEmpty(