一、題目
◆3.21③ 假設表達式由單字母變量和雙目四則運
算算符構成。試寫一個算法,將一個通常書寫形式
且書寫正確的表達式轉換為逆波蘭式。
實現下列函數:
char *RPExpression(char *e);
/* 返回表達式e的逆波蘭式 */
Stack是一個已實現的棧。
可使用的相關類型和函數:
typedef char SElemType; // 棧Stack的元素類型
Status InitStack(Stack &s);
Status Push(Stack &s, SElemType e);
Status Pop(Stack &s, SElemType &e);
Status StackEmpty(Stack s);
SElemType Top(Stack s);
-------------------------------------------------------------------------------------------------
二、思路
拿到題目,要做的第一件事情,就是搞懂題目究竟要我們做什么,很顯然,題目中的關鍵字是“逆波蘭式”,那么首先我們要搞懂這個概念。
所謂的逆波蘭表示法(Reverse Polish notation,RPN,或逆波蘭記法),是一種數學表達式方式,在逆波蘭記法中,所有操作符置於操作數的后面,因此也被稱為后綴表示法。逆波蘭記法不需要括號來標識操作符的優先級。(摘自維基)
舉個簡單的例子,平常我們寫的數學表達式a+b,就是一種中綴表達式,寫成后綴表達式就是ab+。再舉一個復雜的例子,中綴表達式(a+b)*c-(a+b)/e的逆波蘭式是ab+c*ab+e/-。
在弄清楚概念以及題目的要求之后,接下來就要編寫算法了。那么將一個表達式轉換為逆波蘭式的算法思想是什么呢?
(1)首先,需要分配2個棧,棧s1用於臨時存儲運算符(含一個結束符號),此運算符在棧內遵循越往棧頂優先級越高的原則;棧s2用於輸入逆波蘭式,為方便起見,棧s1需先放入一個優先級最低的運算符,在這里假定為'#';
(2)從中綴式的左端開始逐個讀取字符x,逐序進行如下步驟:
1.若x是操作數,則分析出完整的運算數(在這里為方便,用字母代替數字),將x直接壓入棧s2;
2.若x是運算符,則分情況討論:
若x是'(',則直接壓入棧s1;
若x是')',則將距離棧s1棧頂的最近的'('之間的運算符,逐個出棧,依次壓入棧s2,此時拋棄'(';
若x是除'('和')'外的運算符,則再分如下情況討論:
若當前棧s1的棧頂元素為'(',則將x直接壓入棧s1;
若當前棧s1的棧頂元素不為'(',則將x與棧s1的棧頂元素比較,若x的優先級大於棧s1棧頂運算符優先級,則將x直接壓入棧s1。否者,將棧s1的棧頂運算符彈出,壓入棧s2中,直到棧s1的棧頂運算符優先級別低於(不包括等於)x的優先級,或棧s2的棧頂運算符為'(',此時再則將x壓入棧s1;
(3)在進行完(2)后,檢查棧s1是否為空,若不為空,則將棧中元素依次彈出並壓入棧s2中(不包括'#');
(4)完成上述步驟后,棧s2便為逆波蘭式輸出結果。但是棧s2應做一下逆序處理,因為此時表達式的首字符位於棧底;
-------------------------------------------------------------------------------------------------
三、代碼(C/C++)

1 char *RPExpression(char *e) 2 /* 返回表達式e的逆波蘭式 */ 3 { 4 //棧s1用於存放運算符,棧s2用於存放逆波蘭式 5 Stack s1,s2; 6 InitStack(s1); 7 InitStack(s2); 8 9 //假設字符'#'是運算級別最低的運算符,並壓入棧s1中 10 Push(s1,'#'); 11 12 //p指針用於遍歷傳入的字符串,ch用於臨時存放字符,length用於計算字符串長度 13 char *p=e,ch; 14 int length=0; 15 for(;*p!='\0';p++)//逐個字符訪問 16 { 17 switch(*p) 18 { 19 //遇'('則直接入棧s1 20 case '(': 21 Push(s1,*p); 22 break; 23 //遇')'則將距離棧s1棧頂的最近的'('之間的運算符,逐個出棧,依次送入棧s2,此時拋棄'(' 24 case ')': 25 while(Top(s1)!='(') 26 { 27 Pop(s1,ch); 28 Push(s2,ch); 29 } 30 Pop(s1,ch); 31 break; 32 //遇下列運算符,則分情況討論: 33 //1.若當前棧s1的棧頂元素是'(',則當前運算符直接壓入棧s1; 34 //2.否則,將當前運算符與棧s1的棧頂元素比較,若優先級較棧頂元素大,則直接壓入棧s1中, 35 // 否則將s1棧頂元素彈出,並壓入棧s2中,直到棧頂運算符的優先級別低於當前運算符,然后再將當前運算符壓入棧s1中 36 case '+': 37 case '-': 38 for(ch=Top(s1);ch!='#';ch=Top(s1)) 39 { 40 if(ch=='(') 41 { 42 break; 43 } 44 else 45 { 46 Pop(s1,ch); 47 Push(s2,ch); 48 } 49 } 50 Push(s1,*p); 51 length++; 52 break; 53 case '*': 54 case '/': 55 for(ch=Top(s1);ch!='#'&&ch!='+'&&ch!='-';ch=Top(s1)) 56 { 57 if(ch=='(') 58 { 59 break; 60 } 61 else 62 { 63 Pop(s1,ch); 64 Push(s2,ch); 65 } 66 } 67 Push(s1,*p); 68 length++; 69 break; 70 //遇操作數則直接壓入棧s2中 71 default: 72 Push(s2,*p); 73 length++; 74 } 75 } 76 //若棧s1非空,則將棧中元素依次彈出並壓入棧s2中 77 while(!StackEmpty(s1)&&Top(s1)!='#') 78 { 79 Pop(s1,ch); 80 Push(s2,ch); 81 } 82 //最后將棧s2輸出,逆序排列成字符串; 83 char *result; 84 result=(char *)malloc(sizeof(char)*(length+1)); 85 result+=length; 86 *result='\0'; 87 result--; 88 for(;!StackEmpty(s2);result--) 89 { 90 Pop(s2,ch); 91 *result=ch; 92 } 93 ++result; 94 return result; 95 }
-------------------------------------------------------------------------------------------------
四、總結
對於實現逆波蘭式算法,一開始不懂得概念的時候的確不知道如何入手,在摸清思路后,其實難度並不大,關鍵在於邏輯要清晰,而且要細心,寫這段代碼的時候很痛苦,共用了兩天的時間(真的好菜)。
另摘錄維基及度娘中關於實現逆波蘭式的意義:(摘自百度)
為什么要將看似簡單的中序表達式轉換為復雜的逆波蘭式?原因就在於這個簡單是相對人類的思維結構來說的,對計算機而言中序表達式是非常復雜的結構。相對的,逆波蘭式在計算機看來卻是比較簡單易懂的結構。因為計算機普遍采用的內存結構是棧式結構,它執行先進后出的順序。
逆波蘭式的意義:(摘自維基)
當有操作符時就計算,因此表達式並不是從右至左整體計算而是每次由中心向外計算一部分,這樣在復雜運算中就很少導致操作符錯誤。
堆棧自動記錄中間結果,這就是為什么逆波蘭計算器能容易對任意復雜的表達式求值。與普通科學計算器不同,它對表達式的復雜性沒有限制。
逆波蘭表達式中不需要括號,用戶只需按照表達式順序求值,讓堆棧自動記錄中間結果;同樣的,也不需要指定操作符的優先級。
逆波蘭計算器中,沒有“等號”鍵用於開始計算。
逆波蘭計算器需要“確認”鍵用於區分兩個相鄰的操作數。
機器狀態永遠是一個堆棧狀態,堆棧里是需要運算的操作數,棧內不會有操作符。
教育意義上,逆波蘭計算器的使用者必須懂得要計算的表達式的含義。