計算中綴表達式”可以稱得上是一個特別經典的關於棧的算法題,幾乎在所有數據結構教材中都會涉及,而且很多公司面試或者筆試的時候都會把這道題作為一個考察點。可以說,這是一道必須要掌握的算法題。中綴表達式、后綴表達式等概念在這里就不贅述了,讓我們直奔主題。
題目:輸入一個中綴表達式,計算其結果。
輸入的前提假設:
(1)只考慮+、-、*、/這四種運算符,中綴表達式中只有一種括號:();
(2)輸入的中綴表達式中只有整數,沒有小數;
(3)假定輸入是合法的。
很多文章或課本喜歡一步到位,直接討論如何從中綴表達式計算結果。但對於初學者來說,跨度未免大了點。這里循序漸進,從易到難,先討論如何將中綴表達式轉化為后綴表達式,再討論如何計算后綴表達式。最后在前面兩步的基礎上,討論如何一步到位,直接計算中綴表達式的結果:
一、如何將中綴表達式轉化為后綴表達式
在日常應用中,算術表達式中運算符總是出現在兩個操作數之間,例如5*(7-2*3)+8/2,這種形式稱為中綴表達式。計算一個中綴表達式需要知道運算符的優先級和結合性。乘除是高優先級,加減是低優先級,優先級相同時他們都是左結合的,也就是從左計算到右。有括號就要計算括號內的表達式。
中綴表達式利於人的理解,但不便於計算機的處理。因此需要將中綴表達式轉換成后綴表達式,以方便計算機處理。所謂后綴表達式就是將運算符放在運算數之后。后綴表達式也稱為逆波蘭表達式。
比如:
中綴表達式為:1+(2-3)*4+4/2
對應后綴表達式為:1 2 3 - 4* + 4 2 / +
如何將一個中綴表達式轉化為后綴表達式?我們需要借助棧的力量,用它來存放運算符。算法流程如下:
首先將各種運算符(包括括號)的優先級排列如下(數字越大,優先級越高):
1:(
2:+ -
3:* /
4:)
對輸入的中綴表達式從左到右遍歷:
1)如果遇到數字,直接添加到后綴表達式末尾;
2)如果遇到運算符+、-、*、/:
先判斷棧是否為空。若是,則直接將此運算符壓入棧。若不是,則查看當前棧頂元素。若棧頂元素優先級大於或等於此操作符級別,則彈出棧頂元素,將棧頂元素添加到后綴表達式中,並繼續進行上述判斷。如果不滿足上述判斷或者棧為空,將這個運算符入棧。要注意的是,經過上述步驟,這個運算符最終一定會入棧。
3)如果遇到括號:
如果是左括號,直接入棧。如果是右括號,彈出棧中第一個左括號前所有的操作符,並將左括號彈出。(右括號別入棧)。
4)字符串遍歷結束后,如果棧不為空,則彈出棧中所有元素,將它們添加到后綴表達式的末尾,直到棧為空。
二、計算后綴表達式
后綴表達式的計算就相當簡單了。准備一個數字棧。從左到右掃描后綴表達式,如果是數字,放入數字棧。如果是符號,從數字棧中彈出兩個數字,第一個取出的數字為右運算數,第二個為左運算數,進行運算。然后將結果放進數字棧中。如此反復,直到讀完整個表達式后,留在數字棧中的那個數字就是最終結果。
C++代碼如下,要注意,下面的代碼默認中綴表達式中所有數字都是整數,並且都在0到9之間。而且計算結果都是整數(比如5/2=2)。
#include<iostream> #include<string> #include<stack> using namespace std; int getPriority(char ch) { //獲取優先級 if (ch == '(') return 1; else if (ch == '+' || ch == '-') return 2; else if (ch == '*' || ch == '/') return 3; else return 4; } string getPostfixExpression(string str) { //將中綴表達式轉化為后綴表達式 //默認輸入是合法的 stack<char> mystack; int size = str.size(); int i = 0; char tmp; string res = ""; while (i < size) { if (str[i] >= '0' && str[i] <= '9'){ res.push_back(str[i]); } elseif (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/') { if (mystack.empty()) { mystack.push(str[i]); } else { while (!mystack.empty()) { tmp = mystack.top(); if (getPriority(tmp) >= getPriority(str[i])) { //彈出棧頂元素 res.push_back(tmp); mystack.pop(); } else break; } mystack.push(str[i]); } } else { if(str[i]=='(') mystack.push(str[i]); else { while (mystack.top() != '(') { tmp = mystack.top(); res.push_back(tmp); mystack.pop(); } mystack.pop(); } } i++; } //遍歷完后,若棧非空,彈出所有元素 while (!mystack.empty()) { tmp = mystack.top(); res.push_back(tmp); mystack.pop(); } return res; } int calculator(string str) { //計算后綴表達式的值,默認中綴表達式所有數字都是一位的,在0-9之間 stack<int> mystack; int size = str.size(); int num1, num2, num3; for (int i = 0; i < size; i++) { if (str[i] >= '0' && str[i] <= '9') { mystack.push(str[i] - '0'); } else { num2 = mystack.top(); mystack.pop(); num1 = mystack.top(); mystack.pop(); if (str[i] == '+') { num3 = num1 + num2; } else if (str[i] == '-') { num3 = num1 - num2; } else if (str[i] == '*') { num3 = num1 * num2; } else if (str[i] == '/') { num3 = num1 / num2; } mystack.push(num3); } } return mystack.top(); } int main() { string str="1+(2-3)*4+4/2"; cout <<"中綴表達式為:"<< endl << str << endl; string res = getPostfixExpression(str); cout <<"后綴表達式為:"<< endl << res << endl; int num_res = calculator(res); cout <<"計算結果:"<< endl << num_res << endl; system("pause"); return 0; }
三、直接計算中綴表達式
其實將前面的兩步結合起來,就可以得到直接計算的方法。准備一個數字棧和一個符號棧。
從左到右遍歷中綴表達式。如果遇到數字,入數字棧。
如果遇到符號(四個運算符以及括號),跟前面的“中綴表達式轉后綴表達式”過程一樣,對符號棧進行處理。處理過程中,對每一個出棧的運算符:+ - * /,根據“計算后綴表達式”的方法,計算結果(跟數字棧配合)。
如果遍歷完中綴表達式后符號棧還非空,就繼續出符號棧的運算符,計算,直到符號棧為空。最后數字棧剩下的數字就是結果。
下面給出用C++實現“計算中綴表達式”的代碼,里面考慮了“數字不止1位”,並且用浮點型來表示最終運算結果。要求中綴表達式中只能包含整數和運算符(不能包含小數),並且是合法的。
#include<iostream> #include<string> #include<stack> using namespace std; int getPriority(char ch) { //獲取優先級 if (ch == '(') return 1; else if (ch == '+' || ch == '-') return 2; else if (ch == '*' || ch == '/') return 3; else return 4; } void calculate(stack<double> &mystack, char operation) { double num1, num2, num3; num2 = mystack.top(); mystack.pop(); num1 = mystack.top(); mystack.pop(); if (operation == '+') { num3 = num1 + num2; } else if (operation == '-') { num3 = num1 - num2; } else if (operation == '*') { num3 = num1 * num2; } else if (operation == '/') { num3 = num1 / num2; } mystack.push(num3); } double calculator(string str) { //計算中綴表達式,默認輸入是合法的 stack<double> mystack_number; stack<char> mystack_operation; int i = 0, j; int size = str.size(); char tmp_operation; string tmp_num; while (i < size) { if (str[i] >= '0' && str[i] <= '9') { j = i; while (j < size && str[j] >= '0' && str[j] <= '9') { j++; } tmp_num = str.substr(i, j - i); mystack_number.push(atoi(tmp_num.c_str())); i = j; } else if (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/') { if (mystack_operation.empty()) { mystack_operation.push(str[i]); } else { while (!mystack_operation.empty()) { tmp_operation = mystack_operation.top(); if (getPriority(tmp_operation) >= getPriority(str[i])) { //計算 calculate(mystack_number, tmp_operation); mystack_operation.pop(); } else break; } mystack_operation.push(str[i]); } i++; } else { if (str[i] == '(') mystack_operation.push(str[i]); else { while (mystack_operation.top() != '(') { tmp_operation = mystack_operation.top(); //計算 calculate(mystack_number, tmp_operation); mystack_operation.pop(); } mystack_operation.pop(); } i++; } } //遍歷完后,若棧非空,彈出所有元素 while (!mystack_operation.empty()) { tmp_operation = mystack_operation.top(); //計算 calculate(mystack_number, tmp_operation); mystack_operation.pop(); } return mystack_number.top(); } int main() { string str = "1+(2-3)*4+10/2+2*2+2+2/5"; cout << "中綴表達式為:" << endl << str << endl; double num_res = calculator(str); cout << "計算結果:" << endl << num_res << endl; system("pause"); return 0; }
給出一道思考題:如果加入乘方'^',應該如何處理?要注意,乘方運算是右結合的。
其實很簡單。只有兩處修改:
1)將乘方添加到優先級中:
2:+ -
3:* /
4:^
5:)
2)在讀中綴表達式的時候,如果讀到乘方^,就將它放進符號棧中。因為乘方的優先級是最高的,而且是右結合的,所以無論它前面出現的是什么運算,這些運算都不能執行。而且它本身能否執行也是不知道的,因此只能進棧。