在編程的世界中數據結構和算法老是如影隨行, 難捨難分的.git
棧做爲一種常見的數據結構(抽象數據類型)在程序的世界中有很是的意義.算法
在計算機科學中,棧是一種抽象數據類型(
ADT / Abstract Data Type
),用做數據的集合表示.棧有兩個主要的操做編程
push
用於將元素推入棧中數據結構
pop
用於將元素從棧頂部彈出數據結構和算法
簡單來講,棧就是一個後入先出(LIFO / Last In First Out
)的隊列.現實生活中疊盤子就是一個形象的棧,新盤子只能在頂部堆疊進去,而抽盤子是從頂部一個個抽走.函數
棧在計算機中有很是普遍的應用,好比說函數的調用堆棧.談點更實在的應用的話,棧能夠很是方便的用來作平衡符號, 表達式求值和語法解析.
今天的重點是經過棧實現一箇中綴表達式到後綴表達式的轉換,爲以後的構建表達式樹作鋪墊.指針
使用棧來實現平衡符號其實很是簡單code
遇到
'(', '[', '{'
就將符號推入棧中遞歸遇到
')', ']', '}'
就彈出棧中的一個元素,查看是否匹配隊列處理完全部數據以後棧應該爲空.
這個算法的時間複雜度爲
O(N)
而且這個算法是在線的.
常見的表達式如a + b * c + g / f
在計算這個表達式時,咱們必須明確記住操做符的優先級, +, -
的優先級小於*, /
因此表達式處理上就會比較複雜.若是咱們換種思路,將中綴表達式轉換爲後綴表達式,那處理就會簡單不少.
這種記法其實就是將咱們口頭上的說法搬到紙上.
b和c相乘 => b c *
(A1)
a與b和c相乘的結果相加 => a (A1) +
(A2)
g被f除 => g f /
(A3)
前面的結果與後面的結果相加 => A2 A3 +
將A2 A3 +
展開以後就獲得了a b c * + g f / +
最後展開的結果就是中綴表達式a + b * c + g / f
轉換成後綴表達式的結果a b c * + g f / +
,這種記法叫作後綴記法或者逆波蘭記法.
計算這個表達式的值的簡單方法就是使用棧.
當遇到一個操做數時就將這個操做數推入棧中
當遇到一個操做符時就從棧中彈出兩個操做數, 並將這個操做符運用於彈出的兩個操做數上
這種方法顯而易見的一個大優勢就是沒有必要知道任何優先級規則.並且這個算法的時間複雜度是O(N), 並且是在線的.
那麼問題來了,如何將中綴表達式轉換成後綴表達式呢?
顯然,咱們也能夠經過棧來進行轉換.記住如下幾個規則
當遇到一個操做數時,將這個操做數輸出
當遇到一個操做符時,將棧中大於等於該符號優先級的操做符彈出(並輸出).而後將該操做符壓入棧中
當遇到特殊操做符開括號('(', '[', '{'
)時,將該符號壓入棧中.可是把從這個符號開始當作一個新棧(能夠考慮遞歸的調用棧)
當遇到特殊操做符閉括號(')', ']', '}'
)時,將最新的那個棧中的元素所有彈出(並輸出, 可是不輸出彈出的開括號)
規則就這麼多,是否是感受很難理解?咱們來看個栗子~
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <ctype.h> // 用於判斷字符類型 int sp = -1; // 棧頂指針, 始終指向棧頂, 空棧時指向棧底(0)之下 char symbol_stack[64] = { 0 }; // 顯然,咱們須要個符號棧來存放操做符 // 定義幾個棧操做 void push(char op); // 將符號 op 推入棧中 char pop(void); // 彈出棧頂元素 char top(void); // 查看棧頂的元素 int main(void) { // 定義一個輸入表達式 char *input = "a + b * c - g / f"; // 開始處理輸入表達式 // 咱們只進行四則運算的轉換 for (int index = 0; index < strlen(input); ++index) { char current_character = input[index]; char stack_top_character = top(); // 若是是操做數,那就直接輸出 if (isdigit(current_character) || isalpha(current_character)) { printf("%c ", current_character); continue; } // 若是不是操做數,而且不是 '+', '-', '*', '/', '(', ')', ' ' 那就報錯 if (current_character != '+' && current_character != '-' && current_character != '*' && current_character != '/' && current_character != '(' && current_character != ')' && current_character != ' ') { fprintf(stderr, "Unknown symbol `%c`\n", current_character); exit(1); } // 跳過空格和結束標誌 if (current_character == ' ' || current_character == '\0') { continue; } switch (current_character) { // 特殊操做符 ( case '(': push(current_character); break; // 特殊操做符 ) case ')': // 彈出新棧的全部元素, 或者直到爲空棧 while (stack_top_character != '(' && stack_top_character != '\0') { printf("%c ", stack_top_character); stack_top_character = pop(); } pop(); // 彈出多餘的 ( break; case '*': case '/': // 除了 '(' ')' 以外, * / 具備最高的優先級, 除非棧中不可能有比 * / 更大的優先級 // 因此只能彈出優先級相等的操做符(自己) while (stack_top_character == '*' || stack_top_character == '/') { printf("%c ", pop()); stack_top_character = top(); } // 執行完以後壓入當前的符號 push(current_character); break; case '+': case '-': // + - 具備最低的優先級, 因此彈出全部的操做符,除非是表明新棧的 ( while (stack_top_character != '(' && stack_top_character != '\0') { printf("%c ", pop()); stack_top_character = top(); } // 執行完以後壓入當前的符號 push(current_character); break; } } // 最後執行清空全部棧 while (top() != '\0') { printf("%c ", pop()); } return 0; } // 入棧操做定義 void push(char op) { symbol_stack[++sp] = op; } // 出棧操做定義 char pop(void) { return symbol_stack[sp--]; } // 查看操做定義 char top(void) { return sp == -1 ? '\0' : symbol_stack[sp]; }
看我代碼是否是感受就一目瞭然了呢?若是仍是不懂的話,那是個人鍋-^-(面壁思過).其實本身再紙上演算一下就好啦~接下來就是使用轉換出的後綴表達式構建表達式樹了,其實也是用棧的方式啦~