資料結構(8)--棧的應用之行編輯程式、括號匹配檢驗、數制轉換、hanio塔問題
參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社
1.行編輯程式
1.1問題描述
一個簡單的行編輯程式的功能是:接收使用者從終端的輸入的資料,並存入使用者的資料區。而使用者在終端可能輸入差錯,所以每接收一個字元就立即存入資料區顯然是不合適的。此時就需要有一些“回退”或者“撤銷”的操作,即我們需要維護一個“輸入緩衝區”,用以接收使用者的一行資料,然後逐行存入使用者資料區。當用戶輸入時,允許使用者輸入出差錯,並能及時更正。例如:
1.當用戶發現剛剛輸入的一個字元是錯的,可以補進一個"#"表示退格符,用以表示前面輸入的那個字元無效;
2.當用戶發現鍵入的行內差錯較多或難以補救時,可以鍵入"@"表示退行符,用以表示當前一整行均無效。
1.2程式碼實現:
#coding=utf-8 def lineEdit(): #利用棧stack作為一個使用者資料輸入緩衝區,從終端接受一行資料並傳送至呼叫過程的資料區 stack = [] line = raw_input("") #'$'為全文結束符,line不是空串 while len(line)!=0: for i in range(len(line)): ch = line[i] if ch == '$': break; elif ch == '#': stack.pop() elif ch == '@': #清空stack stack = [] else: #有效字元進棧 stack.append(ch) #一行輸入處理完了,將從棧底到棧頂的字元傳送至呼叫過程的資料區,該過程用列印語句代替 print ''.join(stack) #清空棧 satck = [] #讀入下一行 line = raw_input("") def main(): lineEdit() if __name__ == '__main__': main()
1.3演示
2.括號匹配檢驗
2.1問題描述
三種括號:()、[ ]、{ },依次讀入括號串,後遇到的左括號總是“越急迫的渴望得到匹配”,反而先遇到的左括號的急迫程度低,這與棧的特點相吻合。演算法的基本思想是:左括號總是入棧,棧頂的左括號的急迫程度最高,如果接下來讀取的字元是左括號,則該左括號入棧,成為最高急迫渴望得到匹配,而之前的左括號的急迫程度均降低一層;若接下來讀取的字元是右括號,則棧頂的左括號得到匹配,出棧‘以此類推,直到棧為空,則括號是完全匹配的。
演算法流程圖如下:
2.2程式碼實現
#coding=utf-8 def isMatch(topElem, curChar): matcher = {')':'(', ']':'[', '}':'{'} return topElem == matcher[curChar] def isParenthesisMatching(): parenthesisStr = raw_input("請輸入待檢驗的括號串:") #維護一個順序棧stack stack = [] for i in range(len(parenthesisStr)): ch = parenthesisStr[i] if ch == '(' or ch == '[' or ch == '{': stack.append(ch) elif ch == ')' or ch == ']' or ch == '}': #棧是否為空,如果為空,則右括號多餘 if len(stack) == 0: return False #與棧頂元素匹配,則出棧 elif isMatch(stack[-1], ch): stack.pop() #與棧頂元素不匹配,則入棧 else: stack.append(ch) #print stack #如果棧為空,則是完全匹配 return len(stack) == 0 def main(): print isParenthesisMatching() if __name__ == '__main__': main()
2.3演示
3.數制轉換
3.1問題描述
將一個十進位制數N轉化為d進位制數,轉化過程是用N不斷除以d,將所得的餘數記錄下來,直至商為0,產生的餘數逆向組合起來便是對應的d進位制數了。這個過程剛好和棧的後進先出的特點相吻合。
演算法:
1.初始化一個空棧
2.N不斷除以d,求餘數,至商為0(迴圈)
3.產生的餘數依次入棧
4.出棧的順序剛好是轉化好的數(即從棧頂到棧底)
3.2程式碼實現
#coding=utf-8
#數值轉換
def conversion():
stack = []
n = input("請輸入一個待轉換的十進位制數字:")
d = input("想轉化為幾進位制?")
if n == 0:
return 0
while n:
stack.append(n%d)
n/=d
stack.reverse()
return ''.join(map(str, stack))
def main():
print conversion()
if __name__ == '__main__':
main()
3.3演示
4.Hanoi塔問題
4.1問題描述
古代有一個梵塔,塔內有三個座A、B、C,A座上有64個盤子,盤子大小不等,大的在下,小的在上(如圖)。有一個和尚想把這64個盤子從A座移到B座,但每次只能允許移動一個盤子,並且在移動過程中,3個座上的盤子始終保持大盤在下,小盤在上。在移動過程中可以利用B座,要求列印移動的步驟。
漢諾塔問題的求解示意圖:
分析:
1.多個函式巢狀呼叫的內部執行過程
通常,當在一個函式的執行期間呼叫另一個函式時,在執行被呼叫函式之前,系統需要先完成三件事:
(1)將所有的實在引數、返回地址等資訊傳遞給被呼叫函式儲存;
(2)為被呼叫函式的區域性變數分配儲存區;
(3)將控制轉移到被呼叫函式的入口。
而從被呼叫函式返回呼叫函式之前,系統也應完成三件工作:
(1)儲存被調函式的計算結果;
(2)釋放被調函式的資料區;
(3)依照被調函式儲存的返回地址將控制轉移到呼叫函式。
當有多個函式構成巢狀呼叫時,按照“後呼叫先返回”的原則,上述函式之間的資訊傳遞和控制轉移必須通過“棧”來實現,即系統將整個程式執行時所需的資料空間安排在一個棧中,每當呼叫一個函式時,就為它在棧頂分配一個儲存區,每當從一個函式退出時,就釋放它的儲存區,則當前正執行的資料區必在棧頂。
2.遞迴函式的內部執行過程
在計算機內部,一個遞迴函式的呼叫過程類似於多個函式的巢狀呼叫,只不過呼叫函式和被呼叫函式是同一個函式。為了保證遞迴函式的正確執行,系統需設立一個工作棧。具體地說,遞迴呼叫的內部執行過程如下:
⑴ 執行開始時,首先為遞迴呼叫建立一個工作棧,其結構包括值參、區域性變數和返回地址;
⑵ 每次執行遞迴呼叫之前,把遞迴函式的值參和區域性變數的當前值以及呼叫後的返回地址壓棧;
⑶ 每次遞迴呼叫結束後,將棧頂元素出棧,使相應的值參和區域性變數恢復為呼叫前的值,然後轉向返回地址指定的位置繼續執行。
這些執行過程都有系統的工作棧來維護。
4.2程式碼實現
Hanoi問題的遞迴演算法
#coding=utf-8
#count為全域性變數,記錄搬動次數
count = 0
def move(x, n, z):
#搬動操作
global count
count +=1
print "%d. 將%d號盤子從塔座%s搬到%s" %(count, n, x, z)
#將x塔座上按直徑由小到大且自上而下編號為1--n的n個圓盤按規則搬到塔座z上,y可作為輔助塔座
def hanoi(n, x, y, z):
if n == 1:
move(x, n, z)
else:
hanoi(n-1, x, z, y)
move(x, n, z)
hanoi(n-1, y, x, z)
def main():
hanoi(3, 'a', 'b', 'c')
if __name__ == '__main__':
main()
4.3演示
我們為Hanoi問題的核心遞迴演算法語句編上號,如下
1 def hanoi(n, x, y, z):
2 if n == 1:
3 move(x, n, z)
else:
4 hanoi(n-1, x, z, y)
5 move(x, n, z)
6 hanoi(n-1, y, x, z)
那麼上述漢諾塔演算法在執行過程中,工作棧的變化如下圖所示,其中棧元素的結構為(返回地址,n值,A值,B值,C值),返回地址對應演算法中語句的行號,圖的序號對應遞迴呼叫和返回的序號。