波蘭式、逆波蘭式與表達式求值


波蘭式、逆波蘭式是《數據結構》課程中講解關於棧的時候提到的,棧是很簡單的一種數據結構。但是這些理論的提出卻是計算機早期發展領域的重大突破,值得仔細回味。

1. 中綴表達式

我們在數學中學到的表達式被稱為中綴表達式,操作符號在操作數中間,比如 2 + 3 * (5 - 1)。對人類而言,這種表達方式顯而易見,求值也很直接,先算乘除再算加減,先算括號內再算括號外。

然而,這個表達式對於計算機而言卻很費解。你可能會有疑問:這有什么難理解的嘛,在JavaScript、Python或者Ruby,甚至是Java里面都可以通過eval("2 + 3 * (5 - 1)")來計算這個表達式。當然,這里的計算機並不是指現而今強大的計算機和高級編程語言,而是指上個世紀中頁還處於發展初期的計算機。

2. 前綴表達式

早在1920年,波蘭科學家揚·武卡謝維奇就發明了一種不需要括號的表示法,可以用來表示一個計算表達式。即將操作符號寫在操作數之前,也就是前綴表達式,即波蘭式(Polish Notation, PN)。這種表達式直到1960年計算機出現后才發揮出其威力。

比如2 + 3 * (5 - 1)這個表達式的前綴表達式為+ 2 * 3 - 5 1來表示。

閱讀這個表達式需要從左至右讀入表達式,如果一個操作符后面跟着兩個操作數時,則計算,然后將結果作為操作數替換這個操作符和兩個操作數,重復此步驟,直至所有操作符處理完畢。從左往右依次讀取,直到遇到- 5 1,做計算后,將表達式替換為+ 2 * 3 4,然后從左往右再次讀取,直到遇到* 3 4,做計算后將表達式替換為+ 2 12,然后從左往右依次讀取,讀到+ 2 12,計算得到14,到此結束。

可以看到,這種計算過程也相當復雜,需要多次遍歷表達式,而且需要識別一個操作符后面跟着兩個操作數這種模式,相比而言,下文中的逆波蘭式要更為直接和簡單。

如果你熟悉各種編程語言的話,這很像Lisp語言中的表達式(如下代碼)。需要注意的是,Lisp語言中的括號並不是數學意義上的的括號,Lisp中的函數是可以攜帶多個參數的,比如(+ 1 2 3),因此需要使用括號來標明函數參數。

Clojure1.5.1 user=>(+2(*3(-51)))14

3. 后綴表達式

后綴表達式也稱為逆波蘭式(Reverse Polish Notation, RPN),更加廣為人知一些,和前綴表達式剛好相反,是將操作符號放置於操作數之后,比如2 + 3 * (5 - 1)用逆波蘭式來表示則是:2 3 5 1 - * +

逆波蘭式的計算也是從左往右依次讀取,當讀到操作符時,將之前的兩個操作數做計算,然后替換這兩個操作數和操作符,接着讀取,重復此步驟。對於這個表達式,讀到5 1 -,得到4,然后讀取乘號,取出前面的3和上一步的計算結果4,並計算,到12,接着讀取加號+,計算2 12 +得到14,計算結束。

上面這個步驟可以很容易的用棧來實現:

從左往右依次讀取表達式,如果是數字則將該數字壓棧,如果是符號,則將之前的兩個數字出棧,做計算后,將計算結果壓棧,直到表達式讀取結束。棧中剩下的一個數就是計算結果。

逆波蘭式看起來像波蘭式反過來,比如5 + 1的波蘭式是+ 5 1,逆波蘭式為5 1 +或者1 5 +。也很明顯,逆波蘭式並不是簡單的將波蘭式反過來,因為,減法和除法中減數和被減數、除數與被除數是不能交換的,即- 10 5- 5 10就完全不一樣。

4. 中綴表達式到后綴表達式的轉換

因為通過后綴表達式來進行計算只需要一個棧即可,從硬件和軟件上實現都是極為便利的,因此逆波蘭式在計算機領域的應用更加廣泛,因此將中綴表達式轉換為逆波蘭式非常重要。

依然僅僅使用棧就可以將中綴表達式轉換成逆波蘭式,轉換過程如下:

從左往右遍歷中綴表達式中的每個數字和符號,弱是數字就輸出,成為逆波蘭式的一部分; 如果是右括號,或者是其他符號並且比當前棧頂符號的優先級低,則棧頂元素依次出棧並輸出; 然后將當前符號進棧,重復以上操作直到結束。

還是以2 + 3 * (5 - 1)為例:

  1. 首先讀入數字2,直接將其輸出,輸出為2,棧為空
  2. 接着讀入加號+,由於棧為空,因此將其進棧,輸出為2,棧為+
  3. 接着讀入數字3,直接將其輸出,輸出為2 3,棧為+
  4. 接着讀入乘號*,比棧頂元素優先級高,進棧,輸出為2 3,棧為+ *
  5. 讀入左括號(,直接進棧,輸出2 3,棧為+ * (
  6. 讀入數字5,直接將其輸出,輸出為2 3 5,棧為+ * (
  7. 讀入減號-,棧頂元素為左括號,進棧,輸出為2 3 5,棧為+ * ( -
  8. 讀入數字1,直接將其輸出,輸出為2 3 5 1,棧為+ * ( -
  9. 讀入右括號,依次輸出棧頂元素,直到左括號,括號不輸出,輸出2 3 5 1 -,棧為+ *
  10. 已經無元素可讀,依次輸出棧頂元素,直到棧為空,輸出2 3 5 1 - * +,棧為空

這樣可以僅僅使用棧,首先將中綴表達式轉換為逆波蘭式,然后用本文第3節中的方法對后綴表達式進行求值,整個過程使用棧來完成即可。

5. 表達式樹與逆波蘭式

還可以通過另外一種方法來將一個表達式轉換成波蘭式和逆波蘭式,這種方法依賴與樹,首先需要根據表達式構建成樹,仍然以2 + 3 * (5 - 1)為例,下圖是其表達式樹。

表達式樹

我們發現這個樹的后序遍歷結果為2 3 5 1 - * +,剛好是其逆波蘭式;而其先序遍歷結果為+ 2 * 3 - 5 1剛好為其波蘭式;中序遍歷就不用說了,就是我們常見的中綴表達式。我們也可以通過這種特性來實現表達式的各種表示方法的轉換。

6. 參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM