眾所周知啦,我們數學里面的公式就是中綴表達式(infix),形如a*(b+c),支持括號用於調整運算的順序。我們平常用的就是中綴表達式。
那么什么是后綴表達式(postfix)?
后綴表達式(又稱為逆波蘭reverse polish)就是不需要括號就可以實現調整運算順序的一種技法。
比如:ab+cde+**
上面就是一個典型的后綴表達式,將它改為中綴表達式其實是(a+b)*((d+e)*c)。我們這樣將后綴表達式轉換成中綴表達式,雖然符合我們的數學計算習慣,但是並不符合計算機運算的方式。我們可以通過數據結構中的棧來理解后綴表達式和它為什么符合計算機計算。
什么是棧(stack)呢?簡單的說就是LIFO(后入先出)。畫一張圖就是下面這樣
堆棧的操作其實很簡單,你可以把堆棧想象成一個漢諾塔,你可以在塔頂不斷加上漢諾圈,可以從頂部一個一個拿走,但是你不能直接從下面拿走最先放的那個圈。所以你懂了吧,要想拿到最先那個,你必須取出所有的圈,而你最后擺上去的圈可以被立刻拿到。這就是LIFO的本質。
堆棧的實現方法可以是鏈表或者數組。它們各有各得優點,它們的對比網絡上有很多啦,當然我以后也會寫出來。
現在你知道了棧這種結構就可以重新理解一遍后綴表達式。我們就以 "ab+cde+**" 這個表達式舉例。我們創建一個棧,然后從這個表達式開頭(當然是從左邊)掃描,如果掃描到的是數據就壓(push)到棧中。那么在掃描兩個字符后棧里情況是這樣:
接着我們發現了一個操作符(operator),是一個加號(+)。對於每個操作符我們進行如下操作:
1. 從棧中取出兩個操作數,比如這里的a,b
2. 對這兩個操作數進行操作符的運算,如這里的a+b
3. 將操作結果壓到棧中,如假設這里的結果m(m=a+b),將m壓到棧中
此時棧中的情況是這樣的:
重復上述的過程,你可以發現后綴表達式實際上計算了中綴表達式的運算式。
我們會發現當我們掃描到整個后綴表達式的結尾的時候,我們正好計算完整個表達式,因此對於擁有N個字符的后綴表達式,我們計算它的時間是O(N),即為線性時間。
好了,后綴表達式的運算就是這樣的了,現在我們來說一下中綴表達式的運算。
我們依舊使用(a+b)*(c*(d+e))來舉例。對於中綴表達式我們這里采用轉換為后綴表達式的方法,依舊使用堆棧來實現計算。
先介紹一下手寫時轉換的方法
對於((a+b)*(c*(d+e)))將其中的操作符提到對應的括號的后面,得到像這樣的中間轉換式
((ab)-(c(de)+)*)*
然后我們把這個中間轉換式去掉括號,得到后綴表達式
ab-cde+**
現在我們來實現計算機利用堆棧的轉換方式,轉換規則如下:
1. 我們規定+-的優先級為1,*/的優先級為2,( 左括號的優先級為9,) 右括號的優先級為0。
2. 我們規定只有高優先級的運算符可以壓棧,低優先級或者同優先級運算符直接輸出。
3. 我們規定( 壓棧后,下一個運算符(右括號除外)均可以壓棧。
4. 我們規定遇到) 后,一直彈出棧內的操作符直到對應的( 出現。
5. 我們規定操作數均輸出,當掃描完成彈出棧中所有元素。
我們使用圖例來直觀展示一下:
上面就是中綴表達式轉為后綴表達式的方法了。我們發現轉換的時間復雜度也是O(N)。
接下來我給大家介紹一下一個神奇的數據結構,他可以通過不同遍歷方式組合成中輟表達式和后綴表達式。他就是——二叉樹!
二叉樹定義:每個節點最多有兩個子樹的樹結構。關於它的知識實在太多,我在這里不一一介紹,大家可以從網上搜集相關資料。
先給大家看一下二叉樹表示我們舉例的那個算式:
我們先嘗試一下中序遍歷,什么是中序遍歷?你可以簡單的認為是先左,再根,最后右的遍歷順序。注意樹結構的思維方式是遞歸,因此我們從最左邊的a開始遍歷,遍歷順序依次為a——》+——》b——》*——》c——》*——》d——》+——》e。在遍歷的時候你可以認為某一部分是一個整體,這樣嚴格遵循left-parent-right的方式就可以得到中綴表達式了。
那么最后當然是符合計算機計算的后綴表達式,我們使用后序遍歷(先左再右最后根)。遍歷順序是a——》b——》+——》c——》d——》e——》+——》*——》*
ps:其實通過前序遍歷,我們可以得到前綴表達式,不過這玩意真沒多大用,愛鑽研的各位可以自己搜一下。
好的,我的思考就到這里了……百尺竿頭,今進一步!