# [資料結構] CH4. Stacks * Stack(堆疊)是一個可以儲存資料的有序空間,我們可以在裡面加入資料或刪除資料。 * 在這裡你可以先將想成是有特殊性質的**陣列**,下面的範例或圖片也都會先以陣列的樣子做展示。 * 然而Stack在記憶體空間中不一定是連續的,我們也可以藉由**Linked List**等其他方式來實作,這會在更之後的部分才會提到。 * Stack就像是一個細長的桶子,在裡面加入資料時只能從上面加入,而資料不停地往上堆,而最早加入的會被壓在最下面。 * Top指的是最上面的資料,也就是我們唯一可以拿出來的資料(其他的資料被壓住了)。 ![](https://i.imgur.com/H8NkKU3.png) * 最先進去的卻最後才能出來,因此Stack是一種<b>後進先出(Last-In-First-Out)</b>的結構。 * 在Array中大概長成這樣: ```graphviz digraph stack { node[shape=record] stack [label="{ <f0>Data_n|<f1>...|<f2>...|<f3>data3|<f4>data2|<f5>data1}"]; array [label="<f0>data1|<f1>data2|<f2>data3|<f3>...|<f4>...|<f5>Data_n"]; stack:f5->array:f0 stack:f4->array:f1 stack:f3->array:f2 stack:f0->array:f5 Top[shape=""] Top->array:f5 Top->stack:f0 } ``` ## Stack Permutation * 給一序列資料,再給一個空的Stack,我們可以藉由push進去pop出來的動作,交換原本資料的前後順序,而這就被稱作**Stack Permutation**。 * 來個常見的例題: `For a given sequence of elements {𝐴, 𝐵, 𝐶}, please write down its stack permutation.` * 答案(請反白): * <font color="#FFFFFF">ABC;push 𝐴, pop 𝐴, push 𝐵, pop 𝐵, push 𝐶, pop C</font> * <font color="#FFFFFF">ACB;push 𝐴, pop 𝐴, push 𝐵, push C, pop C, pop B</font> * <font color="#FFFFFF">BAC;push 𝐴, push 𝐵, pop B, pop A, push C, pop C</font> * <font color="#FFFFFF">BCA;push 𝐴, push 𝐵, pop B, push C, pop C, pop A</font> * <font color="#FFFFFF">CBA;push 𝐴, push 𝐵, push C, pop C, pop B, pop A</font> ## Stack的應用 ### function的堆疊指標 * 假設某程式function A call了Function B時,電腦必須記住我們Function A是執行到哪裡,以便Function B執行完畢後,知道要回到哪裡繼續執行。 * 在CH1中講到的遞迴程式,系統就是利用堆疊的結構儲存剛剛執行到哪裡,然後在遞迴的function結束時回到剛剛的地方。 * 以階層的程式為範例,在Visual Studio 2017中,使用break point也提供堆疊指標的除錯功能: ```C++==1 #include <iostream> using namespace std; int factorial(int a) { if (a == 0)return 1; else return factorial(a - 1)*a; } int main() { cout << factorial(5); return 0; } ``` ![](https://i.imgur.com/FuHlqLA.png) ### Expression * 一個數學式子$A\div B-C+D\times E-A$,我們把那些加減乘除稱作**運算子(Operator)**,而ABC那些數字稱作**運算元(Operand)**。 * 我們知道一段式子對於每個運算子來說,是有先後順序的,例如先乘除、後加減等。 * 當一段式子變得更加複雜時,我們人類可能都會忘記先計算括號內的東西,電腦來說一定更難看懂。 * 因此,我們可以利用堆疊的結構,將一般常用的式子(又稱**中序表示式(infix)**),轉換成電腦比較好理解的**後序表示式(postfix)**或**前序表示式(prefix)**。 #### postfix and prefix * 一般的式子叫中序表示法,那是因為我們是將運算子放在兩個運算元中間做運算;同理後序與前序便是將運算子放在後面與前面。 * infix: $A\div B-C+D\times E-A$ * postfix: $AB\div C-DE\times+A-$ * prefix: $-+-\div ABC\times DEA$ * 在學會如何用code轉換之前,先來學學如何在考卷上自己轉換: * 中序轉後序: ``` 1.把式子各處按照優先度加上括號。 2.把運算子替換到離它最近的右括號(一樣要先做括弧內的東西)。 3.移除所有括號。 ``` 1.$A\div B-C+D\times E-A$ 2.$((((A\div B)-C)+(D\times E))-A)$ 3.$((((AB\div C-(DE\times +A-$ 4.$AB\div C-DE\times +A-$ * 中序轉前序: ``` 1.把式子各處按照優先度加上括號。 2.把運算子替換到離它最近的左括號(一樣要先做括弧內的東西)。 3.移除所有括號。 ``` 1.$A\div B-C+D\times E-A$ 2.$((((A\div B)-C)+(D\times E))-A)$ 3.$-+-\div AB)C)\times DE))A)$ 4.$-+-\div ABC\times DEA$ ##### 用畫&看快速得到答案: * 如果你已經學過Tree,且你可以從中序式直接想出下面這棵樹: ```graphviz digraph Tree{ 1 [label="-"]; 2 [label="+"]; 3 [label="A"]; 4 [label="-"]; 5 [label="×"]; 6 [label="÷"]; 7 [label="C"]; 8 [label="D"]; 9 [label="E"]; 10 [label="A"]; 11 [label="B"]; 1 -> 2 1 -> 3 2 -> 4 2 -> 5 4 -> 6 4 -> 7 5 -> 8 5 -> 9 6 -> 10 6 -> 11 } ``` * 那麼恭喜你,只要再加上一條線,你已經得到它的中序、前序、後序: ![](https://i.imgur.com/hzcSPYo.png) * 如果要得到**前序**,只要被這條線經過**左邊**的node,你就把它寫下來。 * 如果要得到**中序**,只要被這條線經過**下面**的node,你就把它寫下來。 * 如果要得到**後序**,只要被這條線經過**右邊**的node,你就把它寫下來。 ## 簡單地用陣列實作Stack * 標頭與定義: ```C++=1 #include <iostream> using namespace std; //The size of your stack. #define MAXSIZE 3 int myStack[MAXSIZE], top = -1; void push(int myStack[], int val); int pop(int myStack[]); int peek(int myStack[]); void display(int myStack[]); ``` * push ```C++=11 void push(int myStack[], int val) { if(top == MAXSIZE - 1) { cout << "Stack overflow" << endl; } else { top++; mystack[top] = val; } } ``` * pop ```C++=23 int pop(int myStack[]) { int val; if(top == -1) { cout << "Stack underflow" << endl; return -1; } else { val = mystack[top]; top--; return val; } } ``` * display ```C++=38 void display(int myStack[]) { if(top == -1)cout << "Stack is empty" <<endl; else { cout << i << ": " << mystack[i] << endl; } } ``` * peek ```C++=46 int peek(int myStack[]) { if(top == -1) { cout << "Stack is empty" <<endl; return -1; } else return myStack[top]; } ``` * 上面這些只是個概念,正常當然不會這樣做,但對理解Stack的結構和特性還是有所幫助。 * 實際上C++中,vector就是一個幫你實作好的stack結構,除了上面那些還有很多其他功能,歡迎自己多多研究。 ## Expression Convertor * 最後我們要使用程式來幫助我們轉換前中後序。 * 先講中序轉後序: ``` 1.加入一個")"到式子的尾巴。 2.Push一個"("到stack裡面 3.For each char in infix 如果遇到"(":將它放入stack中。 如果遇到一個運算元:將它放入postfix中(也就是結果)。 如果遇到")": a) 不停將stack的東西pop到postfix中,直到pop出"("。 b) "("請不要放入postfix,因為最後結果不會有括號。 如果遇到一個運算子: a) 將該運算子與stack的top比較優先度: I) 若stack的top優先度較小,將stack pop出來並加到postfix中。完成後繼續回到a)。 II) 若stack的top優先度較大或相等,將此運算子push到stack中。 ``` * 如果你已經會中序轉後序,中序轉前序鐵定難不倒你: ``` 1. 將infix前後顛倒,記得將"("和")"做反向。 2. 將該式做"中序轉後序"。 3. 將輸出結果再次顛倒。 ``` * 該部份如果要做範例很麻煩也很占版面,在此附上本人的code,還請需要者自行參閱。 * [我丟~](https://github.com/Zero871015/ExpressionConvertor) * 我有做step by step,以方便理解。 ![](https://i.imgur.com/xBmMP7b.png) ###### tags: `DS` `note`