計算中綴表示式

凜冬烈焰發表於2018-05-06

“計算中綴表示式”可以稱得上是一個特別經典的關於棧的演算法題,幾乎在所有資料結構教材中都會涉及,而且很多公司面試或者筆試的時候都會把這道題作為一個考察點。可以說,這是一道必須要掌握的演算法題。中綴表示式、字尾表示式等概念在這裡就不贅述了,讓我們直奔主題。

題目:輸入一箇中綴表示式,計算其結果。

輸入的前提假設:

(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)將乘方新增到優先順序中:

1:(

2:+ -

3:* /

4:^

5:)

2)在讀中綴表示式的時候,如果讀到乘方^,就將它放進符號棧中。因為乘方的優先順序是最高的,而且是右結合的,所以無論它前面出現的是什麼運算,這些運算都不能執行。而且它本身能否執行也是不知道的,因此只能進棧。

大家自己試試吧~要記住,學習程式設計,動手去寫程式碼永遠是第一位的。

【參考資料】

[1]https://blog.csdn.net/sinat_36118270/article/details/70257547

[2]翁惠玉, 俞勇. 資料結構:思想與實現[M]. 高等教育出版社, 2009.

相關文章