1.什麼是線性結構
線性結構是一種有序數據項的集合,其中每個數據項都有唯一的前驅和後繼(除了第一個沒有前驅,最後一個沒有後繼)。新的數據項加入到數據集中時,只會加入到原有某個數據項之前或之後。具有這種性質的數據集,就稱爲線性結構。
不同線性結構的關鍵區別在於數據項增減的方式。有的結構只允許數據項從一端添加,而有的機構則允許數據項從兩端移除。
2.從4個最簡單但功能強大的結構入手,開始研究數據結構(棧Stack、隊列Queue、雙端隊列DQeque、列表List)
2.1 棧
2.1.1 什麼是棧?
一種有次序的數據項集合。在棧中,數據項的加入和移除僅發生在同一端。這一端叫棧頂(top),另一端叫棧底(base)
距離棧底越近的數據項留在棧中的時間越長,而新加入棧的數據項會被最先移除。這種次序通常稱爲“後進先出”(Last in First out,LIFO)。這是一種基於數據項保存時間的次序,時間越短的離棧頂越近,而時間越長的離棧底越近。
2.1.2 抽象數據類型“棧”定義的操作
操作樣例
2.1.3 用python實現ADT Stack
#定義ADT Stack
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)#返回棧的大小
#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())
2.1.4 棧的應用:簡單括號匹配
如何構造括號匹配識別算法?
從左到右掃描括號串,最新打開的左括號應該匹配最先遇到的有括號。這樣,第一個左括號(最先打開)就應該匹配最後一個右括號(最後遇到)。這種反轉的識別,正好符合棧的特性。
#定義ADT Stack
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)#返回棧的大小
#括號匹配問題
def parChecker(symbolString):
s=Stack()#創建一個空棧
balanced=True#爲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('(()'))
通用括號匹配算法
#定義ADT Stack
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)#返回棧的大小
#通用括號匹配問題
def parChecker(symbolString):
s=Stack()#傳建一個空棧
balanced=True#爲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):
balanced=False
index=index+1
if balanced and s.isEmpty():
return True
else:
return False
def matches(open,close):
opens="([{"
closers=")]}"
return opens.index(open)==closers.index(close)
print(parChecker('{{([][])}()}'))
print(parChecker('([)'))
2.1.5 棧的應用:十進制轉二進制
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)
#十進制轉二進制
def divideBy2(number):
remstack=Stack()
while number>0:
rem=number%2 #number除以2取餘數
remstack.push(rem)
number=number//2#number除以2求商
#將保存在棧中的數據輸出
binString=""
while not remstack.isEmpty():
binString=binString+str(remstack.pop())
return binString
print(divideBy2(42))
十進制轉十六進制以下的任意進制
#定義ADT Stack
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)
#十進制轉爲十六進制以下的任意進制
def baseConverter(number,base):
digits="0123456789ABCDEF"
remstack=Stack()
while number>0:
rem=number%base #number除以base取餘數
remstack.push(rem)
number=number//base#number除以base求商
#將保存在棧中的數據輸出
newString=""
while not remstack.isEmpty():
newString=newString+digits[remstack.pop()]
return newString
print(baseConverter(25,2))#十進制轉二進制
print(baseConverter(25,8))#十進制轉八進制
print(baseConverter(25,16))#十進制轉十六進制
2.1.6 棧的應用:通用的中綴轉後綴算法
我們通常看到的表達式像這樣:BC,很容易知道這是B乘以C。這種操作符(operator)介於操作數(operand)中間的表示法稱爲中綴表示法。但是有時候中綴表示法會引起混淆,如"A+BC"表示A+B然後再乘以C還是B*C然後再加A?
例如中綴表達式A+B
將操作符移到前面,變成“+AB”;
或者將操作符一道最後,變爲“AB+”
我們就得到了表達式的另外兩種表示法:“前綴”和“後綴”表示法(以操作符相對於操作數的位置來定義)。
舉例
中綴表達式轉換爲前綴和後綴形式的步驟
(1)將中綴表達式轉換爲全括號形式
(2)將所有的操作符移動到子表達式所在左括號(前綴)或者右括號(後綴)處,替代之,再刪除所有括號。
通用的中綴轉後綴算法
後面的算法描述中,約定中綴表達式是由空格隔開的一系列單詞(token)構成。操作符單詞包括*/±(),而操作數單次則是單字母標識符A、B、C等
(1)創建空棧opstack用於暫存操作符,空表postfixList用於保存後綴表達式。
(2)將中綴表達式轉換爲單詞(token)列表
A+BC=split=>[‘A’,’+’,‘B’,’’,‘C’]
(3)從左到右掃描中綴表達式單詞列表
- 如果單詞是操作數,則直接添加到後綴表達式列表的末尾
- 如果單詞是左括號“(”,則壓入opstack棧頂
- 如果單詞是右括號“)”,則反覆彈出opstack棧頂操作符,加入到輸出列表末尾,直到碰到左括號
- 如果單詞是操作符“*/±”,則壓入opstack棧頂(但是咋壓入之前,要比較其與棧頂操作符的優先級,如果棧頂的高於或低於它,就要反覆彈出棧頂操作符,加入到輸出列表末尾,直到棧頂的操作符優先級低於它)
(4)中綴表達式單詞列表掃描結束後,把opstack棧中的所有剩餘操作符依次彈出,添加到輸出列表末尾。
(5)把輸出列表再用join方法合併成後綴表達式字符串,算法結束。
實例
python代碼實現
#定義ADT Stack
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)#返回棧的大小
#通用的中綴表達式轉爲後綴表達式
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棧頂
opStack.push(token)
elif token==')':# 如果單詞是右括號“)”,則反覆彈出opstack棧頂操作符,加入到輸出列表末尾,直到碰到左括號
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():#把opstack棧中的所有剩餘操作符依次彈出,添加到輸出列表末尾。
postfixList.append(opStack.pop())
return " ".join(postfixList)#合成後綴表達式字符串
print(infixToPostfix('A * B + C * D'))
2.1.7 後綴表達式求值問題
在對後綴表達式從左到右掃描的過程中,由於操作符在操作數後面,所以要暫存操作數,在碰到操作符的時候再將暫存的兩個操作數進行實際的計算。
實例
後綴表達式求值算法流程
(1)創建空棧operandStack用於暫存操作數
(2)將後綴表達式用split方法解析爲單詞(token)的列表
(3)從左到右掃描單詞列表
- 如果但此時一個操作數,將單詞轉換爲整數int,壓入operandStack棧頂
- 如果單詞是一個操作符(*/±),就開始求值,從棧頂彈出2個操作數,先彈出的是右操作數,後彈出的是左操作數,計算後將值重新呀入棧頂。
(4)單詞列表掃描結束後,表達式的值就在棧頂
(5)彈出棧頂的值,返回
python代碼實現
#定義ADT Stack
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)#返回棧的大小
#後綴表達式求值
def postfixEval(postfixExpr):
operandStack=Stack()
tokenList=postfixExpr.split()
for token in tokenList:
if token in "0123456789":#如果是操作數,將單詞轉爲整數int,壓入operandStack棧頂
operandStack.push(int(token))
else:
operand2=operandStack.pop()#操作符右邊的操作數
operand1=operandStack.pop()#操作符左邊的操作數
result=doMath(token,operand1,operand2)
operandStack.push(result)
return operandStack.pop()
def doMath(op,op1,op2):
if op=="*":
return op1*op2
if op=="/":
return op1/op2
if op=="+":
return op1+op2
else:
return op1-op2
print(postfixEval("4 5 6 * +"))