1. 程式人生 > >Stack by pointer

Stack by pointer

這就是 one 進行 all 實例 一點 post 知識 誤操作

前言:因為棧的很多操作是基於表的,所以這篇文章裏的例程就不再大面積地寫註釋了,有不理解的地方可以翻看之前的鏈表筆記,或者直接寫在評論區。

咳咳說到這個棧,很多人乍聽之下感覺很陌生、臥槽這是什麽玩意。其實生活中隨處可見,在一些小餐館,客人不多的時候,椅子都是放成一摞的,一個疊一個。有客人來了就搬下來一把——肯定是搬最上面那一把,沒人會從下面搬凳子吧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的循環了但是這種實現方法的缺點在於對mallocfree的調用開銷是昂貴的。避免這個缺點的方法就是用數組實現,具體的實現方法以後會說到,在後面幾篇文章裏會詳細討論棧的應用和數組實現。

下面寫了一個測試程序,比較簡陋,你們不要嫌棄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