逆波蘭表達式
C語言講解【複合複製運算符】時會提到,複合運算簡練並且產生機器碼的效率高。爲什麼效率高呢,
這就必須要了解:【編譯原理】中提到的【逆波蘭式】。逆波蘭表達式又叫做【後綴表達式】。推而廣之必然還
存在【前綴表達式】、【中綴表達式。】(有時也成爲,前序式,中序式,後序式)。中綴表達式就是我們常用的
所謂的標準的表達式如"A+B",它在數學上學名叫中綴表達式(Infix Notation),原因是:
運算符號在兩個運算對象的中間。
其優勢: 在於只:用兩種簡單操作,入棧和出棧就可以搞定任何普通表達式(僅包含:+-*/和()的表達式)的運算。
其基本運算方式 :如果當前字符爲變量或者爲數字,則壓棧,如果是運算符,則將棧頂兩個元素彈出作相應運算,結果再入棧,最後當表達式掃描完後,棧裏的就是結果。
爲什麼說逆波蘭式產生機器碼的效率高:因爲逆波蘭式非常易於計算機的處理。原因是這樣的。舉例:
3 32 + 5 3 * -
12 34 2 - * 8 /
乍一看上面兩個式子很奇怪,是嗎?它們就是一種表達式的記法——逆波蘭表達式。
現在,準備一個很窄的圓筒,筒是有底的,像一個細長的杯子,粗細剛好和一枚硬幣相當。再做幾個和硬幣一樣大的小圓紙片,在紙片上依次寫上“3”“32”“+”“5”“3”“*”“-”,記住,每個紙片上要麼只寫一個數,要麼只寫一個運算符號,把它們按上面的順序排好。好,現在仔細聽我說,按順序一個接一個地拿起小圓紙片,反覆執行以下幾個規則:
1. 如果你拿着的是一個數,不多說,直接把它放進圓筒;
2. 如果你拿着的是一個運算符號,不要把它放進去。先從圓筒裏取出兩個數(當然是先取最上而的啦,筒很細的),然後處這兩個數作運算符號指定的運算,並把結果寫在一張新的紙片上,然後放進筒裏。比如你拿着的是“+”,你要依次取出“32”和“3”,讓它們相加,得“35”,把“35”寫在一張新紙片上(現在“34”和“12”可以扔掉了),並把這張新紙片放進圓筒。
當圓筒裏只有一個數時,你就可以停下來了,我猜這個數是20,沒錯,這就是這個表達式的值!
我們剛纔操作的,其實就是一個“棧”,棧是一種數據結構,具有一個性質——後進先出(LIFO——Last Input First Output),你已經深有體會了,就像一摞盤子,你只能從最上面的開始取,放的時候也只能放在最上面。放進去的動作叫做“入棧”,取出來叫做“彈出”。以後你就可以把棧想像成一摞盤子,或是上面說的小圓筒和小紙片,棧就是這麼簡單!
逆波蘭表達式雖然看起來比較繁瑣,其實在計算機中很有用。計算機可不知道先乘除後加減,先括號內後括號外,它要把你輸入的式子變成逆波蘭表達式,它就可以不斷地執行上面兩個固定的規則,直至把結果算出來告訴你。,編譯器在處理時候按照從左至右的順序讀取逆波蘭表達式,遇到運算對象直接壓入堆棧,遇到運算符就從堆棧提取後進的兩個對象進行計算,這個過程正好符合了計算機計算的原理。所以,逆波蘭式非常適宜計算機的處理。
那麼,接下來的問題就是:
將一箇中綴表達式 轉換成 逆波蘭式的算法: 結合一個具體例子分析如下:
a)給出一箇中綴表達式1*(2+3)
b)系統先定義兩個先進後出的堆棧:運算符號棧(簡稱入棧in),後綴表達式輸出符號棧(簡稱出棧out)
c)系統按從左至右的順序讀取中綴表達式
d)讀入數字直接壓入出棧(out)
e)讀入第一個運算符直接壓入入棧(in)
f)讀入"("直接壓入入棧(in)。 按上述規則讀取若干次後,若,此時兩棧的數據爲: in 【*,( 】 ; out 【1,2】,開始讀取的第二個的運算符"+",並將之與入棧(in)中的棧頂運算符"("進行比較,
g)高於棧頂運算符級別的算符直接進棧,低於或等於棧頂級別的要將入棧(in)解棧(即出棧),按次壓入出棧(out)中。比如現在入棧的運算順序爲(,*,/,此時若系統讀取的運算符爲+,級別比/要低,此時要按/,*的順序壓入出棧out中,並在入棧中釋放/和*符號,最後得到 in ( ; out /,*的結果。
f)最後讀取")"時要找到入棧in中最近的"(",將其前面所有符號全部按後進先出的順序壓入出棧,並解壓,"("與")"抵消。此時兩棧的數據爲:in 1,2,3,+ ; out *
g)系統讀取中綴表達式結束後將入棧in中的所有符號按後進先出的順序全部解壓,並依次壓入出棧out中,最後出棧的結果就應該爲1,2,3,+,*
h)按先進先出的順序將出棧out解壓得到後綴標準表達式1,2,3,+,*
兩個堆棧先後數據情況:
In |
out |
1 |
|
* |
1 |
*,( |
1 |
*,( |
1,2 |
*,(,+ |
1,2 |
*,(,+ |
1,2,3 |
* |
1,2,3,+ |
1,2,3,+,* |
將中綴表達式轉換成逆波蘭表達式過程中,特別要注意對於中綴標到式中括號的處理。
1、要注意的,如果算符是"(",無論入棧中棧頂級別(只看棧頂)爲何直接入棧,所以,“(”的等級
只用於對其後入棧的算符進行優先級比較,在“(”入棧時是無視優先級的。
注: 逆波蘭用的優先級有以下幾種: 等級從高-->低 是: 1、* \ 2、+ - 3、( 4、)
2、在遇到")"時候找到最後進入的"(",並把"("前面所有的符號都壓入出棧。不能僅憑運算符的級別來判斷。
將一個 逆波蘭式 倒轉回 中綴表達式 的算法:
這個就相當簡單了,就是一個機械的入堆棧出堆棧的操作,
1)設置一個堆棧,將逆波蘭式從左到右開始進行出入堆棧操作,還以上例爲例:1,2,3,+,*
2)遇到數字直接壓棧;例如,上例逆波蘭先進行三次入棧操作,堆棧的格局是: 1,2,3(棧頂);
3)遇到算符,將堆棧中的兩個數字出棧。 如,讀到+號後,2,3出棧,進行運算。注意,出棧時先出棧的元素是右算子,後出棧的是左算子,上例是2+3,不是3+2;
4)將運算的結果作爲新的算子,壓入堆棧中。如運算結果(2+3)入棧,堆棧格局:1,(2+3);
5)反覆1-4的操作,得到的中序表達式就是: 1*(2+3);
中序表式生成的逆波蘭式唯一嗎?:
是唯一的,和固定形式的中序表達式一一對應,但,請注意這個概念,
例如: a+(b-c)*d 和 (b-c)*d+a 和 a+d*(b-c) 的值是完全一樣的。但是,他們的中序形式不同,
產生的逆波蘭式必然是不同的。
a+(b-c)*d : abc-d*+
(b-c)*d+a : bc-d*a+
a+d*(b-c) : adbc-*+