我們今天繼續看一下,如何使用棧完成標準的四則混合運算表示式求值。
不同於字尾表示式,遇到乙個運算子就可以直接從棧裡取兩個數進行運算。在標準的四則混合運算表示式中(或者我們稱之為中綴表示式),遇到乙個操作符是不能直接計算的,因為計算的順序要取決於後面的運算子。多舉幾個例子,大家就能明白了。由於加和減是相同的運算優先順序,乘和除是相同的運算優先順序,我們就只用加和乘來舉例就可以了。
我們像計算字尾表示式一樣,引入乙個運算元棧。由於字尾表示式不用快取 +-*/ 這些操作符,所以乙個運算元棧就夠了。但是,中綴表示式的計算是要用到操作符的快取的,所以,我們還要再引入乙個操作符棧,專門儲存各個操作符。
第乙個例子:a + b * c,我們從前向後掃瞄,遇到a,壓到運算元棧裡,遇到 + ,壓到操作符棧裡,下乙個是 b,此時,我們是不能像字尾表示式求值那樣,直接計算 a + b,然後壓棧的。因為我們不確定,b 是否會先參與後面的運算,所以我們只能把 b 先壓棧,繼續向後掃瞄。看到 * 以後,再把 * 壓到操作符棧裡;看到 c 以後,也要把 c 先壓棧,理由與 b 壓棧相同。接下來,再往下掃瞄,就發現已經到了末尾了。那我們就可以放心地從操作符棧裡取出頂上的操作符,在這個例子中,就是 *,然後再從運算元棧裡取出兩個運算元,就是 b 和 c,然後把b和c的積,不妨記為d,放到運算元棧裡。接下來,再去看操作符棧裡,還有乙個 +,那就把 + 取出來,然後去運算元棧裡取出 a 和 d,計算它們的和,這就是最終的結果了。
第二個例子:a + b + c,我們從前向後掃瞄,遇到a,壓到運算元棧裡,遇到 + ,壓到操作符棧裡,下乙個是 b,壓到運算元棧裡。再下乙個,又是 + ,由於加法的結合律,我們知道,先把a + b 的結果計算出來,或者後計算,都不會影響最終的結果。所以我們就把能做的化簡先做掉。就是說,在掃瞄到第二個操作符是 + 以後,我們就把第乙個操作符取出來,再把兩個運算元取出來,求和並且把和送到運算元棧裡。接下來的過程與第乙個例子是相同的,不再贅述了。
通過這兩個例子,我們看到,乙個操作符究竟什麼時候進行運算,並不取決於它前面的那個操作符是什麼,而是取決於它後面的那個操作符是什麼。更具體一點講:如果後面的操作符的運算優化級比前面的操作符高,那麼前面的操作符就必須延遲計算;如果後面的操作符優化級比前面的低或者相等,那麼前面的操作符就可以進行計算了。上面這句話,非常重要,是我們這節課的核心。請多讀幾遍,結合上面的兩個例子,務必想明白它。
好了,理解了這個,就可以上**了。
先看 stack的完整定義:
class stack public t gettop() public void push(t t) public t pop() public boolean isempty() }
然後我們建立兩個棧,乙個是運算元棧,乙個是操作符棧:
public class stackexpression }
其中,tokenstream 是這節課裡的課後作業:介面卡模式
按照上面分析的演算法,如果遇到數字,就壓棧到numbers裡,如果遇到操作符,就要看前面乙個的操作符的優先順序是否比當前操作符高,如果前乙個操作符高,那麼執行前乙個操作符的操作,如果是後面的高,那就只要把後面的操作符壓棧即可:
public class stackexpression else else } } while (!operators.isempty()) system.out.println("result is " + numbers.gettop()); } private static void binarycalc(stack numbers, stack operators) private static int preorder(token.tokentype left, token.tokentype right) else return 1; }}
好了。我們的這個程式已經可以處理類似 a + b - c * d + e / f 這樣的算式了。
為了讓大家看得更清楚,我用圖把 a + b * c / d 的過程畫一遍。
首先,遇到 a ,把 a 送到運算元棧,遇到 + ,送到操作符棧:
遇到 b,壓棧
遇到乘,由於乘的優先順序高於加,所以,現在就什麼也不做,只把乘號進棧:
同樣,遇到 c 把 c 進棧(此圖略,請自己補上),再遇到 / ,由於除的優先順序與 * 的優先順序相同,所以,乘就可以先做了。這個動作是把乘號出棧,把c 和 b出棧,求 c * b的值,並且把這個值入棧。即:
然後把 / 入棧,把 d 入棧:
現在到了運算的結尾了。我們只需要把現在的棧裡的內容從頂向下計算起來即可,先算除法:
再算加法:
但是,括號怎麼辦?
可以這樣想,在遇到形如 a * (b + c) 這樣的形式的時候,左邊的乘法是一定不能做的,我們只需要將左括號進棧即可。所以,我們可以把tokenstream裡取得的左括號看做是乙個優先順序無窮大的運算子,它使得左方的運算子都不能提前進行計算。
遇到右括號時,b + c 這個加法是可以進行運算的了,所以可以把右括號看作是乙個優先順序無窮小的運算子,它會使得操作符棧上的所有運算子都出棧並執行計算,直到遇到左括號。遇到左括號以後,只需要把左括號從棧裡彈出來,然後讓它和右括號一起狗帶即可(這就是括號匹配啊,同學們~)。由於括號內的計算都已經完成了,結果是乙個整數,我們已經在計算的過程中把這個整數放到運算元棧裡了。所以整個括號內的求值就完成了。
好了。今天的課程很短,但是比較難理解,尤其是,今天的程式都依賴於本周前邊的課程。請務必先完成本週前邊的課程再來看這節課。
演示一下,我的完整版的效果:
用棧計算表示式
首先宣告我們的表示式 expression 只是簡單的四則運算,再加上小括號.運算元operand,操作符operator 如果直接給出字尾表示式 postfix,也叫逆波蘭式 是最容易計算的,這種表示式中已經不含括號.方法 遇到運算元時壓入棧中 遇到操作符時從棧中彈出兩個元素進行此運算,再將運算結...
用棧計算表示式
首先宣告我們的表示式 expression 只是簡單的四則運算,再加上小括號.運算元operand,操作符operator 如果直接給出字尾表示式 postfix,也叫逆波蘭式 是最容易計算的,這種表示式中已經不含括號.方法 遇到運算元時壓入棧中 遇到操作符時從棧中彈出兩個元素進行此運算,再將運算結...
C 用棧計算表示式
基本思路 1 設定兩個棧,乙個運算子棧,乙個運算元棧。初始化後將 壓入操作符棧中。2 順序掃瞄 當輸入為運算元時就將其壓入運算元棧。當輸入為運算子時,則比較輸入運算子和運算子棧的棧頂運算子的優先順序的大小。若輸入運算子的優先順序高於運算子棧頂運算子的優先順序時,則將其壓入到運算子棧 若運算子棧頂運算...