棧ADT前端
棧(stack)是限制插入和刪除只能在一個位置上進行的表,該位置是表的末端,叫作棧頂。棧的基本操做有進棧(push)和出棧(pop),前者至關於插入,後者至關於刪除最後的元素。在最後插入的元素能夠經過使用Top例程在執行Pop以前進行考查。對空棧進行的Pop或Top通常被認爲是棧ADT的錯誤。另外一方面,當運行Push時空間用滿是一種實現錯誤,但不是ADT的錯誤。算法
棧有時又叫作LIFO(後進先出表)。數組
棧的實現函數
因爲棧是一個表,所以任何實現表的方法都可以實現棧。兩種流行的方法:一種是使用指針實現,一種是使用數組實現。工具
棧的鏈表實現測試
在表的頂端插入實現Push,在表的頂端刪除實現Pop,Top只是返回頂端元素,有時Top和Pop兩個也能夠合二爲一。ui
棧ADT鏈表的聲明實現spa
struct Node; typedef struct Node *PtrTONode; typedef PtrToNode Stack; struct Node{ ElementType Node; PtrToNode Next; };
測試棧是否爲空操作系統
int IsEmpty(Stack S){ return S->Next == NULL; }
建立一個空棧也很簡單,咱們只要創建一個頭結點,MakeEmpty設置Next指針指向NULL。Push是做爲向鏈表前端進行插入而實現的,其中,表的前端做爲棧頂。Top的實現是返回表的前端的元素,Pop是經過刪除表的前端元素實現。設計
建立一個空棧的過程
Stack CreateStack(void){ Stack S; S = malloc(sizeof(struct Node)); if(S == NULL){ printf(」 out of space」); } S->Next == NULL; MakeEmpty(S); return S; }
void MakeEmpty(Stack S){ if(S == NULL){ Error(); }else{ while(!IsEmpty(S)){ Pop(s); } }
Push進棧例程
void Push(ElememtType X, Stack S){ PtrToNode TemCell; TemCell = malloc(sizeof(struct Node)); if(TemCell == NULL){ Error(); }else{ TemCell->ElementType = X; TemCell->Next = S->Next; S->Next = TemCell; } }
Pop操做實現
ElementType Top(Stack S){ if(!IsEmpty(S)) return S->Next->Element; Error(); return 0; }
對於鏈表的實現,全部的操做基本上都只花費常數的時間,上述的操做出了空棧以外都沒有涉及到棧的大小,更沒有依賴棧進行循環了。這種實現的缺點是對於malloc和free操做是昂貴的開銷。有的缺點能夠經過兩個棧進行避免,第二個棧初始化爲空棧,當單元彈出時,它只是被放入到第二個棧,此後當須要新空間時,首先檢查第二個空棧。
棧的數組實現
數組實現避免了指針操做而且是更流行的實現,惟一的不足是它先要聲明一個數組的大小。一般棧的實際個數並不會太大,聲明一個合理的空間沒有什麼困難。若是不能的話,那就採用鏈表實現。數組實現棧是很是簡單的,每個棧都有一個TopOfStack,空棧時爲-1,當某個元素壓入棧時,將TopOfStack加1,而後至Stack[TopOfStack] = X;其中,Stack就是具體棧的數組。出棧時,咱們返回Stack[TopOfStack]的值,而後TopOfStack減1,爲了Stack和TopOfStack相對應,它們應該是棧結構的一部分。
上述的操做不只以常數時間運行,並且是以很是快的時間運行。在現代化的計算機中,棧已經成爲操做系統指令的一部分。一個影響棧執行效率的問題是錯誤檢查。
棧的聲明
struct StackRecord; tepedef struct StructRecord * Stack; struct StackRecord{ int Capacity; int TopOfStack; int ElementType *Array; }
Stack CreateStack(int MaxElement){ Stack S; if(MaxElement < MinStackSize) Error(); S = malloc(sizeof(struct StackRecord)); if(S == NULL) Error(); S->Array = malloc(sizeof(ElementType) * MaxElement); if(S->Array == NULL) Error(); S->Capacity = MaxElements; MakeEmpty(S); return S; }
檢測棧是否爲空
int IsEmpty(Stack S){ return S->TopOfStack == EmptyTOS; }
建立一個空棧
void MakeEmpty(Stack S){ S->TopOfStack = EmptyTOS; }
進棧操做
void Push(ElementType S, Stack S){ if(IsFull(S)) Error(); else S->Array[++S->TopOfStack] = X; }
返回棧頂元素
ElementType Top(Stack S){ if(!IsEmpty(S)) return S->Array[S->TopOfStack]; Error(); return 0; }
從棧頂彈出元素
void Pop(Stack S){ if(IsEmpty) Error(); else S-TopOfStack—; }
將Top和Pop進行合併
ElementType TopAndPop(Stack S){ if(!IsEmpty(S)){ return S->Array[S->TopOfStack]; } Error(); return 0; }
應用
平衡符號
編譯器檢查你的程序的語法錯誤,當時經常因爲缺乏一個符號形成上百行的錯誤。在這種狀況下,就須要一個工具檢驗成對出現,每個雙符號都要有對應的符號,一個簡單的算法就用到棧,以下描述:
作一個空棧。讀入字符直到文件尾。若是字符是一個開放字符,則將其推入棧中,若是字符是一個封閉符號,則當棧空時報錯。不然,將棧元素彈出,若是彈出的符號不是對應的開放符號,則報錯。在文件尾,若是棧非空則報錯。
上述的算法是線性的,事實上,它只要對輸入進行一趟檢驗。所以,它是在線的,速度很是的快。
後綴表達式
在一個由優先級構成的算術表達式中,咱們一般要根據運算符的有限級進行計算結果。請下面的例子:
4.99 + 5.99 + 6.99 * 1.06 = 18.69
若是沒有考慮優先級的話,計算的結果將是19.37.咱們能夠經過下面的方法進行計算,操做順序以下:
4.99 1.06 * 5.99 +6.99 1.06 * +
上面的記發叫作後綴或者逆波蘭記法。計算這個問題最容易的辦法就是使用一個棧:當碰見數時,就把它放入棧中,在遇到運算符時就做用於棧中彈出的兩個數,並將結果推入棧中。
計算一個後綴表達式的時間是線性的O(N),對輸入的元素由一些棧操做組成從未花費常數的時間,而且沒必要要知道任何的有限順序。
中綴到後綴的轉換
棧不只能夠計算後綴表達式,並且還能夠將一個標準的表達式(中綴表達式)轉換成後綴表達式。以下中綴表達式:
a + b * c + (d * e + f) * g
轉換成後綴表達式:
a b c * + d e * f + g * +
具體操做是:當讀到一個操做數的時候,當即把它放到輸出中,操做符不當即輸出,保存在某個地方,正確的作法是將遇到的操做符保存在棧中,遇到左括號也放入棧中。
若是碰見一個右括號,那麼就將棧元素彈出,將彈出的符號輸出直到碰見相匹配的左括號,可是左括號不進行輸出。
若是咱們碰見任何其餘的符號,那麼咱們從棧中彈出棧元素直到發現優先級更低的元素爲止。有一個例外,除非是一個)的時候,不然咱們毫不從棧中移除(。對於這種操做,+的優先級最低,(優先級最高。當彈出元素結束後,咱們在將操做符移入棧中。
當到達末尾時,咱們將棧中元素彈出,變成空棧,將符號輸出。
一樣,這種轉換隻須要O(N)的,對於運算符時是從左到右的結合的,上面的算法是正確的,否則就須要從新設計。
函數調用
當存在函數調用時,須要存儲重要的信息,諸如寄存器的值,和返回的地址,都要以抽象的方式存在一張紙上並被置於一個堆的頂部。
遞歸的不當使用:打印一個鏈表
void PrintList(List L){ if(L != NULL){ PrintElement(L->Element); PrintList(L->Next); } }
這個程序是尾遞歸,是使用極端不當的例子,尾部涉及在最後一步的遞歸。
尾遞歸能夠經過將遞歸調用變成goto語句並在其前加上對函數每一個參數的賦值語句而手工刪除。它模擬了遞歸調用,由於沒有什麼須要存儲的值,在遞歸調用以後,實際上沒有必要知道存儲的值。下面是經過goto改造的while循環實現:
void PrintList(List L){ top: if(L != NULL){ PrintElement(L->Element); L = L->Next; goto top; } }
遞歸老是可以完全除去,可是有時是至關冗長複雜的。通常方法是使用一個棧來消除,雖然非遞歸確實比遞歸程序要快,可是速度的優點代價確實因爲去除而使得程序的清晰度不足。