shunting-yard 調度場算法、中綴表達式轉逆波蘭表達式
中綴表達式
1*(2+3)
這就是一個中綴表達式,運算符在數字之間,計算機處理前綴表達式和後綴表達式比較容易,但處理中綴表達式卻不太容易,因此,我們需要使用shunting-yard Algorithm(調度場算法)來將中綴表達式轉換為後綴表達式(即逆波蘭表達式),然後求解。
上面的中綴表達式轉後綴表達式後為:
1 2 3 + *
調度場算法
為了將中綴表達式轉為後綴表達式,使用調度場算法,算法思想如下:
準備兩個棧,一個用於存放數字,一個用於存放操作符。
從左到右遍歷表達式,如果是數字,直接入棧。(註意數字可能是多位數甚至小數)
如果是符號:
如果棧為空,或者當前符號為‘ ( ’,或者棧頂為‘ ( ‘,直接入棧。
如果當前運算符優先級高於棧頂運算符的優先級,則入棧。(註意是高於,等於也不行)
如果當前運算符優先級不高於棧頂運算符,先從存符號的棧中取出一個操作符,從存數據的棧中取出兩個數字,將它們做一次運算並將結果壓入數據棧,最後再把當前運算符壓入符號棧。
如果當前符號是‘ ) ‘,不斷重復上面劃線部分的操作,直到取出的操作符是‘ ( ‘為止。
表達式遍歷完後,不斷重復上面劃線部分的操作,直到符號棧為空,此時數據棧還有一個數字,那就是原表達式的值。
這樣就基本完成了。
這裏再來討論一下負號的處理,如何判斷 ‘ - ‘ 是減號還是負號呢?
如果‘ - ‘出現在表達式開頭,一定是負號。
如果‘ - ‘出現在數字後面,一定是減號。
如果‘ - ‘出現在‘ ( ‘後面,一定是負號。
如果‘ - ‘出現在‘ ) ‘後面,一定是減號。
綜上可以看出,只要‘ - ’在表達式開頭或者‘ ( ‘後面就可以判斷為負號,但是判斷之後怎麽處理呢?
有兩種辦法,一種是在判斷為負號的時候將數字0壓入數據棧,然後將負號壓入符號棧,可以看做是零減去一個數字。
另一種辦法是,如果判斷為負號,將下一個壓入數據棧的數字乘上 -1。采用這種方法需要註意,如果括號內只有一個負數而沒有計算式,按照上面的判斷法則,下一個‘ ) ‘將直接被壓入棧,從而導致奇怪的事情發生,只要在‘ ) ‘入棧前特判一下即可避免這種問題。(下面代碼采用第二種方法)
C++代碼實現如下
#include <iostream>
#include <string>
#include <cstring>
#include <stack>
using namespace std;
int op[55]; //確定運算符的優先級
/* string轉數字 */
double toDig(string str) {
double n = 0, mag = 0.1;
for(int i = 0; i < str.length(); i++) {
if(str[i]==‘.‘) break;
mag *= 10;
}
for(int i = 0; i < str.length(); i++) {
if(str[i]==‘.‘) continue;
n += mag*(str[i] - ‘0‘);
mag /= 10;
}
return n;
}
/* 計算並返回結果 */
double getAns(double a, double b, char c) {
switch (c) {
case ‘+‘: return b+a;
case ‘-‘: return b-a;
case ‘*‘: return b*a;
case ‘/‘: return b/a;
}
}
/* shunting-yard */
double shunting(string str) {
stack<double > iStk;
stack<char> strStk;
for(int i = 0; i < str.length();) {
if( (str[i]>=‘0‘ && str[i]<=‘9‘) || (i==0&&str[i]==‘-‘) || (str[i]==‘-‘&&str[i-1]==‘(‘)) {
// 判斷是否為數字或負號
string s1;
int f = 1;
if(str[i]==‘-‘) {
f=-1;
i++;
}
while((str[i]>=‘0‘ && str[i]<=‘9‘) || str[i]==‘.‘) {
s1 += str[i++];
}
iStk.push(f*toDig(s1));
} else { //不是數字或負號,則為操作符或括號
// 如果棧為空、或操作符為‘(‘、或棧頂為‘(‘、或當前操作符的優先級大於棧頂操作符,則操作符入棧
if(strStk.empty() || str[i]==‘(‘ || strStk.top()==‘(‘ || (str[i]!=‘)‘&&op[str[i]] > op[strStk.top()])) {
if(str[i]==‘)‘ && strStk.top()==‘(‘) strStk.pop(); //當前符號和棧頂是一對括號則消除它們
else strStk.push(str[i]);
}
else if(str[i] == ‘)‘) {
// 如果當前是‘)‘,則做運算直到棧頂是‘(‘
char c = strStk.top();
while(c != ‘(‘) {
double a = iStk.top();
iStk.pop();
double b = iStk.top();
iStk.pop();
strStk.pop();
iStk.push(getAns(a,b,c));
c = strStk.top();
}
strStk.pop();
}
else {
// 否則,說明當前運算符優先級等於或小於棧頂運算符,將棧頂操作符取出做一次運算,將運算結果壓棧,最後再將當前操作符入棧
double a = iStk.top();
iStk.pop();
double b = iStk.top();
iStk.pop();
char c = strStk.top();
strStk.pop();
iStk.push(getAns(a,b,c));
strStk.push(str[i]);
}
i++;
}
}
// 表達式處理完後,不斷運算直到操作符空棧,此時數據棧剩下的一個數據就是最終結果
while(!strStk.empty()) {
double a = iStk.top();
iStk.pop();
double b = iStk.top();
iStk.pop();
char c = strStk.top();
strStk.pop();
iStk.push(getAns(a,b,c));
}
return iStk.top();
}
int main() {
string s;
memset(op,1,sizeof(op));
op[‘+‘] = op[‘-‘] = 0;
op[‘*‘] = op[‘/‘] = 1;
while(cin >> s) {
cout << shunting(s) << endl;
}
return 0;
}
shunting-yard 調度場算法、中綴表達式轉逆波蘭表達式