【演算法】E.W.Dijkstra算術表示式求值

Richaaaard發表於2015-07-07

算術表示式求值

我們要學習的一個棧的用例同時也是展示泛型的應用的一個經典例子,就是用來計算算術表示式的值,例如

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

如果將4乘以5,把3加上2,取它們的積然後加上1,就得到了101。但Java系統是如何完成這些運算的呢?不需要研究Java系統的構造細節,我們也可以編寫一個Java程式來解決這個問題。它接受一個輸入字串(表示式)並輸出表示式的值。為了簡化問題,首先來看一下這份明確的遞迴定義:算術表示式可能是一個數,或者是由一個左括號、一個算術表示式、一個運算子、另一個算術表示式和一個右括號組成的表示式。簡單起見,這裡定義的是未省略括號的算術表示式,它明確地說明了所有運算子的運算元——你可能更熟悉形如 1 + 2 * 3 的表示式,省略了括號,而採用優先順序規則。我們將要學習的簡單機制也能處理優先順序規則,但在這裡我們不想把問題複雜化。為了突出重點,我們支援最常見的二元運算子*、+、-和/,以及只接受一個引數的平方根運算子sqrt。我們也可以輕易支援更多數量和種類的運算子來計算多種大家熟悉的數學表示式,包括三角函式、指數和對數函式。我們的重點在於如何解釋由括號、運算子和數字組成的字串,並按照正確的順序完成各種初級算術運算操作。如何才能夠得到一個(由字串表示的)算術表示式的值呢?E.W.Dijkstra在20世紀60年代發明了一個非常簡單的演算法,用兩個棧(一個用於儲存運算子,一個用於儲存運算元)完成了這個任務,其實現過程如下,執行軌跡如輸出結果。

表示式由括號、運算子和運算元(數字)組成。我們根據以下4種情況從左到右逐個將這些實體送入棧處理:

  • 將運算元壓入運算元棧
  • 將運算子壓入運算子棧
  • 忽略左括號
  • 在遇到有括號時,彈出一個運算子,彈出所需數量的運算元,並將運算子和運算元的運算結果壓入運算元棧

在處理完最後一個右括號之後,運算元棧上只會有一個值,它就是表示式的值。這種方法乍一看有些難以理解,但要證明它能夠計算得到正確的值很簡單:每當演算法遇到一個被括號包圍並由一個運算子和兩個運算元組成的子表示式時,它都將運算子和運算元的計算結果壓入運算元棧。這樣的結果就像在輸入中用這個值代替了該子表示式,因此用這個值代替子表示式得到的結果和原表示式相同。我們可以反覆應用這個規律並得到一個最終值。例如,用該演算法計算以下表示式得到的結果都是相同的

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

( 1 + ( ( 5 ) * ( 4 * 5 ) ) )

( 1 + ( 5 * 20 ) )

( 1 + 100 )

101

程式:

 1 package com.beyond.algs4.experiment;
 2 
 3 import com.beyond.algs4.lib.Stack;
 4 import com.beyond.algs4.std.StdIn;
 5 import com.beyond.algs4.std.StdOut;
 6 
 7 public class Evaluate {
 8 
 9     public static void main(String[] args) {
10         Stack<String> ops = new Stack<String>();
11         Stack<Double> vals = new Stack<Double>();
12         // 讀取字元,如果是運算子則壓入棧
13         while (!StdIn.isEmpty()) {
14             String s = StdIn.readString();
15             if         (s.equals("("))                ;
16             else if (s.equals("+"))        ops.push(s);
17             else if (s.equals("-"))        ops.push(s);
18             else if (s.equals("*"))        ops.push(s);
19             else if (s.equals("/"))        ops.push(s);
20             else if (s.equals("sqrt"))    ops.push(s);
21             else if (s.equals(")")) {
22                 // 如果符號為“)”, 彈出運算子和運算元,計算結果並壓入棧
23                 String op = ops.pop();
24                 double v = vals.pop();
25                 if        (op.equals("+"))    v = vals.pop() + v;
26                 else if (op.equals("-"))    v = vals.pop() - v;
27                 else if (op.equals("*"))    v = vals.pop() * v;
28                 else if (op.equals("/"))    v = vals.pop() / v;
29                 else if (op.equals("sqrt"))    v = Math.sqrt(v);
30                 vals.push(v);
31             }
32             else vals.push(Double.parseDouble(s));            
33             StdOut.println("運算元棧:" + vals.toString());
34             StdOut.println("運算子棧:" + ops.toString());
35         }
36         StdOut.println(vals.pop());
37     }
38 
39 }

輸出:

( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
運算元棧:
運算子棧:

運算元棧:1.0 
運算子棧:

運算元棧:1.0 
運算子棧:+ 

運算元棧:1.0 
運算子棧:+ 

運算元棧:1.0 
運算子棧:+ 

運算元棧:2.0 1.0 
運算子棧:+ 

運算元棧:2.0 1.0 
運算子棧:+ + 

運算元棧:3.0 2.0 1.0 
運算子棧:+ + 

運算元棧:5.0 1.0 
運算子棧:+ 

運算元棧:5.0 1.0 
運算子棧:* + 

運算元棧:5.0 1.0 
運算子棧:* + 

運算元棧:4.0 5.0 1.0 
運算子棧:* + 

運算元棧:4.0 5.0 1.0 
運算子棧:* * + 

運算元棧:5.0 4.0 5.0 1.0 
運算子棧:* * + 

運算元棧:20.0 5.0 1.0 
運算子棧:* + 

運算元棧:100.0 1.0 
運算子棧:+ 

運算元棧:101.0 
運算子棧:

 

 

參考資料:

演算法 第四版  謝路雲 譯 Algorithms Fourth Edition [美] Robert Sedgewick, Kevin Wayne著

http://algs4.cs.princeton.edu/home/

原始碼下載連結:

http://pan.baidu.com/s/1c0Ao7Bi

相關文章