1. 棧的基礎
棧,也成為堆棧,是一種重要的線性結構。棧具有線性表的特點:每一個元素只有一個前驅元素和後繼元素(除了第一個元素和最後一個元素外),但是在操作上與線性表不同,棧是一種操作受限的線性表,只允許在棧的一端進行插入和刪除操作。棧可以用順序儲存結構和鏈式儲存結構儲存,採用順序儲存結構的棧稱為順序棧,採用鏈式儲存結構的棧稱為鏈式棧。棧的應用十分廣泛,在運算式求值、括弧匹配時常用到棧的思想。
2. 棧的操作
允許插入和刪除操作的一端稱為棧頂,另一端稱為棧底。棧頂是動態變化的,它由一個稱為棧頂指標(top)的變數指示,當表中沒有元素時,稱為空白棧。棧的插入操作稱為入棧或進棧,刪除操作稱為出棧或退棧。
圖 堆棧示意圖
例如:一個進棧的序列由A、B、C組成,它的出棧序列有ABC、ACB、BAC、BCA和CBA五種可能,只有CAB是不可能的輸出序列。因為ABC進棧後,C出棧接著就是B要出棧,不可能A在B之前出棧,所以CAB是不可能的序列。
棧的基本操作包括以下七種:
(1)InitStack(&S):初始化操作,建立一個空棧S。
(2)StackEmpty(S):若棧S為空白,返回1,否則返回0。
(3)GetTop(S,&e):返回棧S的棧頂元給e。
(4)PushStack(&S,e):在棧S中插入元素e,使其成為新的棧頂元素。
(5)PopStack(&S,&e):刪除棧S的棧頂元素。
(6)StackLength(S):返回棧S的元素個數。
(7)ClearStack(S):清空棧S。
3. 共用棧
棧的應用十分廣泛,經常會出現一個程式需要同時使用多個棧的情況。使用順序棧會由於棧空間的大小難以準確估計,從而造成有的棧溢出,有的棧空間還有空閑,為瞭解決這個問題,可以讓多個棧共用一個足夠大的連續儲存空間,通過利用棧的動態特性使多個棧儲存空間能夠互相補充,儲存空間得到有效利用,這就是棧的共用。
在棧的共用問題中,最常用的是兩個棧的共用,實現方法是兩個棧共用一個一維數組空間S(StackSize),兩個棧的棧底設定在數組的兩端,當有元素進棧時,棧頂位置從棧的兩端迎面增長,當兩個棧的棧頂相遇時,棧滿。共用棧的儲存表示如下圖所示:
圖 兩棧共用空間
如上圖,設兩個棧共用一個一維數組空間Stack[M],將兩個棧的棧底分別在該數組空間兩端,即Stack[O]和Stack[M-1]。這樣,當元素進棧時,兩個棧都是從兩端向中間伸展。通過兩個棧頂指標(top1和top2)的動態變化,使其儲存空間相互補充。
兩棧共用空間的資料結構定義如下: C代碼 #define M 100 //兩棧共用的儲存空間大小 typedef struct { ElemType Stack[M]; //兩棧共用的一維數組空間 int top[2]; //兩棧的棧頂指標 } DSegStack;
4. 棧的鏈式表示與實現
在順序棧中,由於順序儲存結構需要事先靜態分配,而儲存規模往往又難以確定,如果棧空間分配過小,可能會照成溢出,如果棧空間分配過大,又造成儲存空間浪費。因此,為了克服順序儲存的缺點,採用鏈式儲存結構表示棧。
採用鏈式儲存方式的棧稱為鏈棧或鏈式棧。鏈棧由一個個節點構成,節點包含資料域和指標域兩部分。在鏈棧中,利用每一個節點的資料域儲存棧中的每一個元素,利用指標域表示元素之間的關係。
圖 鏈棧示意圖
鏈棧的資料結構定義如下: C代碼 typedef struct node { DataType data; struct node *next; }LStackNode, *LinkStack; //LinkStack為棧頂指標,始終指向鏈棧的頭結點
5. 棧與遞迴
遞迴是指在函數的定義中,在定義自己的同時又出現了對自身的調用。如果一個函數在函數體中直接調用自己,則稱為直接遞迴函式。如果經過一系列的中間調用,間接調用自己的函數稱為間接遞迴調用。遞迴問題可以分解成規模小且性質相同的問題加以解決。
用遞迴編製的演算法具有結構清晰、易讀,容易實現並且遞迴演算法的正確性很容易得到證明。但是,遞迴演算法的執行效率比較低,因為遞迴需要反覆入棧,時間和空間開銷大。遞迴演算法也完全可以轉換為非遞迴實現,這就是遞迴的消除。消除遞迴的方法有兩種,一是對於簡單的遞迴可以直接用迭代,通過迴圈結構就可以消除;另一種方法是利用棧的方式實現。
利用棧類比遞迴過程可以通過以下步驟實現:
(1)設定一個工作棧,用於儲存遞迴工作記錄,包括實在參數,返回地址等。
(2)將調用函數傳遞過來的參數和返回地址入棧。
(3)利用迴圈類比遞迴分解過程,逐層將遞迴過程的參數和返回地址入棧。當滿足遞迴結束條件時,依次逐層退棧,並將結果返回給上一層,直到棧空為止。