Stack by pointer
前言:因為棧的很多操作是基於表的,所以這篇文章裏的例程就不再大面積地寫註釋了,有不理解的地方可以翻看之前的鏈表筆記,或者直接寫在評論區。
咳咳,說到這個棧,很多人乍聽之下感覺很陌生、臥槽這是什麽玩意。其實生活中隨處可見,在一些小餐館,客人不多的時候,椅子都是放成一摞的,一個疊一個。有客人來了就搬下來一把——肯定是搬最上面那一把,沒人會從下面搬凳子吧2333 用完之後從上面再疊放上去,這是一個例子。 刷知乎或者看網頁的時候需要返回,我們按一下,就跳轉到上一個頁面了,那這是怎麽做的呢?我們用直覺考慮一下,應該是瀏覽器把每一次操作的結果都保存下來,要返回的時候,就把當前層移除——移除的是最新的那一層。如果有新的跳轉或者其他操作,就依次疊放到之前最新的上面。類似的,主流的文本編輯器也都支持撤銷操作,我們的編輯操作被記錄在一個棧中,一旦出現誤操作,只需要按下撤銷(一般是control+z)按鈕,就可以取消最近的一次操作,並回到之前的狀態。
而我們在寫程序的時候會涉及到不同函數之間的相互調用,被調函數(callee)執行完後,把權限返還給主調函數(caller)這也用到了“棧”這種結構。許多程序語言本身就是建立於棧結構上的,比如Postscript和Java運行環境都是基於棧結構的虛擬機。
我們再聯系上一節提到的那個“free list”,可以很明顯的感到一個性質:這些行為的次序,都是增加的時候從最新的那一端增加,要移除的時候,往往是把“最後移動的元素”首先給拿出去。這就叫後進先出(Last In First Out)。而且相對於一般的序列結構,它的數據操作範圍都僅限於整個表的末端。
對棧的基本操作有Push(進棧or壓棧)和Pop(出棧),前者相當於插入,後者則是刪除最後的元素。
這是一個進行若幹操作後的抽象棧,一般的模型是存在某個元素位於棧頂,而這是唯一的可見元素。不過這樣說可能有點不好理解,那比如說一摞椅子。
這就可以視作一個棧,為了維持這一放置形式,對這個棧的操作只能在頂部實施:新的椅子只能疊放到最頂端;反過來只有最頂端的椅子才能被取走。因此和這個實例相比照,棧中可操作的一端被叫做棧頂,而另外一個無法操作的盲端被稱為棧底。
就像這樣。
因為棧是一個表,所以任何實現表的方法都能實現棧,這次就說一下好理解的的指針實現吧,比數組貌似好理解一些。用單鏈表實現的話,我們要通過在表頂端插入來實現Push,通過刪除頂端元素實現Pop。而Top操作僅僅是返回頂端元素的值。不過在很多時候都是把Pop和Top合二為一的。本來可以用前一節的代碼段,不過為了清楚起見,還是從頭開始寫吧
和之前一樣,先給出一些前提性聲明,實現棧同樣要用到表頭。
1 struct Node; 2 typedef struct Node *PtrToNode; 3 typedef PtrToNode Stack; 4 struct Node{ 5 int Element; 6 PtrToNode Next; 7 };
測試空棧與測試空表的方式一樣。
1 int IsEmpty(Stack i){ 2 return i->Next==NULL; 3 }
創建一個棧的話也很簡單,只需要建立一個頭結點就好。
1 Stack Creat(){ 2 Stack S; 3 S=malloc(sizeof(struct Node)); 4 if(S==NULL) 5 printf("out of space!!!"); 6 else 7 S->Next=NULL; 8 MakeEmpty(S); 9 return S; 10 }
現在是中場問答時間,我們創建一個棧之後,裏面會有什麽?就是僅僅申請一塊內存,然後什麽也不做。裏面會有——
垃圾數據,對吧。這是上學期的知識,聲明一個變量後,系統會隨機填充一段數據,我們不知道裏面是什麽,但是,我們能確定一點——這東西十有八九不是我們所期望的,因此我們需要把它扔掉。這就是MakeEmpty的意義。
1 void MakeEmpty(Stack S){ 2 if(S==NULL) 3 printf("Must creat a stack first"); 4 else 5 while(!IsEmpty(S)) 6 Pop(S); 7 }
關於這個Pop函數是什麽,emmm接著往後看吧,你看這涉及到了函數間的相互調用,就是運用了棧的特性。還有一個好玩的事實,就是——我們在寫一個棧的時候已經用到了棧的環境,用棧來寫棧,這就陷入遞歸了233 從這個角度再次理解一下遞歸吧,畢竟理解遞歸是篩選合格程序員的一道門檻。
創建之後就該討論對棧的各項操作了,主要就三個:出棧,入棧和取棧頂元素。先說入,有入才有出嘛,Push是作為向鏈表前端進行插入而實現的,其中表的前端作為棧頂。所以實現起來也很順暢
這裏提一句,S是表頭,裏面什麽都不存,而第一個有效元素是S->Next,原因是S僅作為一個地址說明,告訴我們第一個有效元素“在哪”,我們不可能指望S存數據,不然的話,誰來告訴我們這個棧的頂在哪呢?這很重要,理解這個觀點是看懂下面所有函數的基礎,是重中之重。
接著說取棧頂元素,Top的實施是通過考察整個表在第一個位置上的元素而完成的,也就是把Head的元素返回
1 int Top(Stack S){ 2 if(!IsEmpty(S)) 3 return S->Next->Element; 4 printf("Empty stack"); 5 return 0; 6 }
最後,Pop是通過刪除表的前端元素而實現的。
1 void Pop(Stack S) { 2 PtrToNode FirstNode; 3 if(IsEmpty(S)) 4 printf("Empty stack"); 5 else{ 6 FirstNode=S->Next; 7 S->Next=S->Next->Next; 8 free(FirstNode); 9 } 10 }
到這裏,已經很清楚了,所有的操作均花費常數時間,因為這些函數沒有任何地方涉及到棧的size,更不用說依賴於size的循環了。但是這種實現方法的缺點在於對malloc和free的調用開銷是昂貴的。避免這個缺點的方法就是用數組實現,具體的實現方法以後會說到,在後面幾篇文章裏會詳細討論棧的應用和數組實現。
下面寫了一個測試程序,比較簡陋,你們不要嫌棄Orz
1 #include <stdio.h> 2 #include <stdlib.h> 3 struct Node; 4 typedef struct Node *PtrToNode; 5 typedef PtrToNode Stack; 6 struct Node{ 7 int Element; 8 PtrToNode Next; 9 }; 10 //函數簽名 11 int IsEmpty(Stack i); 12 void Push(int X,Stack S); 13 int Top(Stack S); 14 void Pop(Stack S); 15 void MakeEmpty(Stack S); 16 Stack Creat(); 17 void Traverse(Stack S); 18 19 //入口 20 int main(){ 21 Stack S; 22 S=Creat(); 23 int n; 24 printf("Please input all elements to complete a stack,finished by 0\n"); 25 while (scanf("%d",&n)&&n) 26 Push(n, S); 27 Traverse(S); 28 printf("Input imperative(1:top\t2:remove\t3:add),0 to quit\n"); 29 while (scanf("%d",&n)&&n) { 30 if (n==1) 31 printf("Top element:%d\n",Top(S)); 32 else if(n==2){ 33 Pop(S); 34 Traverse(S); 35 } 36 else if(n==3){ 37 printf("number:"); 38 scanf("%d",&n); 39 Push(n, S); 40 Traverse(S); 41 } 42 else 43 printf("Input again,it is invalid"); 44 } 45 } 46 47 //接口內部一覽 48 int IsEmpty(Stack i){ 49 return i->Next==NULL; 50 } 51 void Push(int X,Stack S){ 52 Stack TemCell; 53 TemCell=malloc(sizeof(S)); 54 if(S==NULL) printf("Out of space!!!"); 55 else{ 56 TemCell->Element=X; 57 TemCell->Next=S->Next; 58 S->Next=TemCell; 59 } 60 } 61 62 int Top(Stack S){ 63 if(!IsEmpty(S)) 64 return S->Next->Element; 65 printf("Empty stack"); 66 return 0; 67 } 68 69 void Pop(Stack S) { 70 PtrToNode FirstNode; 71 if(IsEmpty(S)) 72 printf("Empty stack"); 73 else{ 74 FirstNode=S->Next; 75 S->Next=S->Next->Next; 76 free(FirstNode); 77 } 78 } 79 void MakeEmpty(Stack S){ 80 if(S==NULL) 81 printf("Must creat a stack first"); 82 else 83 while(!IsEmpty(S)) 84 Pop(S); 85 } 86 Stack Creat(){ 87 Stack S; 88 S=malloc(sizeof(struct Node)); 89 if(S==NULL) 90 printf("out of space!!!"); 91 else 92 S->Next=NULL; 93 MakeEmpty(S); 94 return S; 95 } 96 void Traverse(Stack S){ 97 for (; S->Next; S=S->Next) { 98 printf("%d->",S->Next->Element); 99 } 100 printf("NULL\n"); 101 }View Code
然後自己調試一下吧,這會加深你對棧的理解的,祝食用愉快~
Stack by pointer