带括号表达式求值
算法1:双栈法 (2019.11)
(1)规定运算符优先级(详见具体操作步骤)
(2)对输入的字符逐一检验
(a)如果是数字字符:按位权转化为数值
(b)如果不是数字字符:将上一步的数值压栈
i 如果是'('或符号栈为空:将该字符压入符号栈
ii 如果是')'或'=':将符号栈中所有符号弹出,每弹出一个符号从数据栈拿出两个数字进行计算,计算结果压入数据栈,直到数据栈为空或栈顶元素为'('为止
iii 其他情况:比较当前元素与栈顶元素的优先级
(i) 如果当前元素优先级 > 栈顶元素优先级:将当前元素压入符号栈
(ii) 如果当前元素优先级 <= 栈顶元素优先级:符号栈弹出一个符号进行运算,直到当前元素优先级 > 栈顶元素优先级
关键点提醒:
使用双栈法时,如何确保表达式是从左往右计算的?这是处理当前元素与栈顶元素优先级相等关系时决定的,如果当前元素优先级小于等于栈顶,则直接进行运算,把运算结果存入数值栈。试想如果等于时不是弹栈进行运算,而是压栈,那么遇到优先级较小的运算符需要弹栈时,则会造成从右向左运算,这种情况下有减法运算时会出错(如3-4+8)
具体操作步骤:
规定运算符优先级:
运算符 |
( |
+ - |
x / % |
^ |
优先级 |
0 |
1 |
2 |
3 |
(1)首先,建立数值栈、符号栈两个栈分别存储操作符和操作数。
(2)将数字字符转化成数值。得到经过预处理的字符串后,从头到尾依次读取单个字符。为了将数字字符转化成数值,我们建立了state_sign和state_fraction两个标志位。一开始,将state_sign标志位置1,表示正在读取数字字符(数字字符还没读完),state_fraction标志位置0,表示即将读取的是一个数整数部分。如果遇到数字,并且state_fraction状态是0,则将单个字符的数值(这里指的是当前数字字符的ASCⅡ编码值减去字符‘0’ ASCⅡ编码值,下同)每次乘以10以后相加(最初d=0,0*10=0);遇到小数点‘.’,则将state_fraction状态置为1,表明即将读取的是一个数的小数部分;在此状态下读取数字字符,则依次将单个字符数值除以10后相加。
(3)将符号字符与符号栈顶字符优先级比较后决定是否存入符号栈。如果遇到的不是数字字符,则将state_sign置为0,表明数字字符已经转化成数值,可以将其放入数值栈中。如果读到的是‘(’,或者符号栈为空,则将当前字符入符号栈,如果遇到的是‘)’或者‘=’,则将符号栈全部出栈,(一次弹出两个,进行计算后存入数值栈)直到符号栈为空,或者栈顶元素为‘(’,如果遇到的不是上述符号并且符号栈不为空,则将当前字符与符号栈栈顶字符优先级进行比较(根据程序需要,我们将字符优先级定义为上面“四.1”所示),如果当前字符优先级小于等于栈顶字符(这里包含等于是为了保证从左到右的运算法则),则直接两次取出数值栈栈顶进行运算,并将运算结果存入栈顶;如果当前字符优先级大于栈顶字符,则把当前字符放入符号栈。
(4)对整个字符串执行上述操作,最终栈顶元素就是最后的计算结果。
代码实现:(可处理异常;支持负数、小数、多位数运算;支持加减乘数乘方模运算功能)

#include<iostream> #include<stack> #include<string.h> #include<math.h> using namespace std; stack<double> data; stack<char> sign; int priority(char ch){ //运算符优先级判断 if(ch == '+'||ch == '-') return 1; if(ch == 'x'||ch == '/'|| ch== '%') return 2; if(ch == '^') return 3; if(ch == '(') return 0; } double aRb(char ch){ //判断运算类型 double a,b; a = data.top(); data.pop(); b = data.top(); data.pop(); //依次取出栈顶两个元素 switch(ch){ case '+' : b += a; break; case '-' : b -= a; break; case 'x' : b *= a; break; case '/' : if(a==0){ throw "Divider can not be zero!"; //除数为0,抛出异常 }else { b /= a; break; } case '%' : if(a==0){ throw "divider can not be zero!"; //除数为0,抛出异常 }else { b = (double)((int)b%(int)a); break; } case '^' : b = pow(b,a); //乘方、开方运算 break; } return b; } int base(char temp){ //判断字符类型 char base_digtal[] = {"1234567890"}; char base_sign[] = {"+-x/%^"}; char base_others[] = {"().="}; if(strchr(base_digtal,temp)) return 1; else if(strchr(base_sign,temp)) return 2; else if(strchr(base_others,temp)) return 3; else return 0; } void showerror(char temp[],int e){ //指出第一次出现错误的位置 for(int i=0;i<strlen(temp);i++){ if(i==e) cout<<temp[e]<<"<| "; else cout<<temp[i]; } cout<<endl; } bool precheck(char temp[]){ stack<char> t; for(int i=0;i<strlen(temp);i++){ if((temp[i]&0x80) && (temp[i+1]&0x80)){//如果字符高位为1且下一字符高位也是1则有中文字符 cout<<"请不要输入中文字符"<<endl; showerror(temp,i+1); return false; } if(temp[i] == '='&& i!=strlen(temp)-1){ //如果等号出现在表达式中间则报错 cout<<"Invalid input"<<endl; showerror(temp,i); return false; } if(!base(temp[i])){ cout<<"Please input the sign appointed!"<<endl;//出现未知符号 showerror(temp,i); return false; } if((temp[i]=='('||temp[i]==')')&&i!=0&&i!=strlen(temp)-1&&base(temp[i-1])==1&&base(temp[i+1])==1){ cout<<"Both sides of parenthesis are numbers!"<<endl; //符号省略 showerror(temp,i); return false; } if(((base(temp[0])==2)&&temp[0]!='-')||(i==strlen(temp)-1&&base(temp[i])==2)||(base(temp[i])==2&&base(temp[i+1])==2)){ cout<<"Signs redundancy!"<<endl;//符号冗余(重复输入) showerror(temp,i); return false; } if(temp[i]=='.'&&base(temp[i+1])!=1){ cout<<"The fraction part you put is wrong!"<<endl;//小数部分输入错误 showerror(temp,i); return false; } if(temp[i]=='(') t.push(temp[i]); if(temp[i]==')') t.pop(); } if(t.empty()) return true; else { cout<<"Parenthesis is not matching!"<<endl;//括号不匹配 return false; } } double calculate(char temp[]){ double d = 0; bool state_fraction = 0;//判断当前数字字符在小数点前面还是后面 bool state_sign = 1;//判断一个数是否读完 int count = 1; for(int i=0;i<strlen(temp);i++){ //把数字字符处理成数值 if(!state_fraction&&base(temp[i])==1){ d *= 10; d += (double)(temp[i] - '0'); state_sign=0; }else if(temp[i]=='.'){ state_fraction = 1; }else if(state_fraction&&(base(temp[i])==1)){ d += (double)(temp[i] - '0')/pow(10,count); count++; state_sign=0; }else{ if(!state_sign) { data.push(d); } d = 0; state_fraction = 0; count = 1; if(temp[i]=='('||sign.empty()){//如果是(或栈为空直接入栈 sign.push(temp[i]); }else if(temp[i]==')'||temp[i]=='='){ //如果遇到)或者=,则符号栈全部出栈,同时从数值栈取出两个数进行运算 while(!sign.empty()&&sign.top()!='('){//根据短路原则,注意顺序 double res = aRb(sign.top()); data.push(res); sign.pop(); } if(!sign.empty()) sign.pop();//弹出( }else if(priority(temp[i])<=priority(sign.top())){ //如果优先级小于等于栈顶,则直接进行运算,把运算结果存入数值栈 //等号的位置决定从左到右进行运算 while(!sign.empty()&&priority(temp[i])<=priority(sign.top())){ double res = aRb(sign.top()); data.push(res); sign.pop(); } sign.push(temp[i]); }else if(priority(temp[i])>priority(sign.top())){ //如果优先级大于栈顶,则入符号栈 sign.push(temp[i]); } state_sign = 1; //遇到非数字字符表示数字部分已读完 } } return data.top(); //栈顶元素即为最终运算结果 } int main() { cout<<"---------Welcome!---------"<<endl; cout<<"----Input exit to exit----"<<endl; double ans; //最终计算结果 while(1){ char temp[300]; //存储读入的字符串,用于预处理等操作 memset(temp,0,300); while(!data.empty()) data.pop(); while(!sign.empty()) sign.pop(); //初始化 cin.getline(temp,300); if(strcmp(temp,"exit")==0) break; //程序出口 //展示更精确的结果 if(strcmp(temp,"show more")==0){ printf("ans = %.9lf\n",ans); continue; } //对空格进行处理 for(int i=0;i<strlen(temp);i++){ if(temp[i]==' ') for(int k=i;k<strlen(temp);k++) temp[k] = temp[k+1]; } if(!precheck(temp)) continue; //预处理结果判断 //对'-'前没有数字的情况进行加0处理 if(temp[0]=='-'){ int k = strlen(temp); for(k;k>0;k--) temp[k] = temp[k-1]; temp[0] = '0'; } for(int i=1;i<strlen(temp);i++){ if(temp[i]=='-'&& base(temp[i-1])!=1&&temp[i-1]!=')'){ int k = strlen(temp); for(k;k>i;k--) temp[k] = temp[k-1]; temp[i] = '0'; } } //如果用户没有输入'=',在字符串末尾添加'=' int k = strlen(temp); temp[k] = '\0'; if(temp[k-1]!='=') { temp[k] = '='; temp[k+1] = '\0'; } //运行异常检测 try{ ans = calculate(temp); cout<<"ans = "<<ans<<endl; }catch(const char*message){ cout<<message<<endl; } } return 0; } //测试数据 //-(-3+(3x(5/2+(3^2%5)+5)x2)/3)+((2x5-3)x2)= //-(-2+3-5)= //12+.3-.5x2 //.3+4-2x(3+5/2^3)-.3 //-3.4+4 //3.22.4+2? //2.3x(3+3.4)-4/2.2 //(2-(-(-(-(-(-2)x3)x1)x.3)/2.3)-2)/2 //2.6125 + 0.04150390625
算法2:基于栈的逆波兰算法 (201911)
算法具体步骤:
(1)建立一个操作符栈(临时存放操作符);再建立一个队列(存放后缀表达式);
再建立一个栈(后缀表达式求值时临时存放操作数);
后缀表达式中有数字(double)有字符(char),后缀表达式要存放在一个栈中,可以将队列的基本类型定义为结构体类型。
(2)将中缀表达式转换成后缀式(逆波兰表达式)
(a) 从左到右读进中缀表达式的每个字符。
(b) 如果读到的字符为操作数,则直接输出到后缀表达式中(注意多位数和小数的处理参照算法1(有优化))。
(c)如果遇到“)”,则弹出栈内的运算符,直到弹出到一个“(”;如果遇到“=”,则弹出栈内所有运算符
(d) “(”的优先级在栈内比任何运算符都小,但可直接入栈
(e)当运算符准备进入栈内时,和栈顶的运算符比较,如果外面的运算符优先级高于栈顶的运算符的优先级,则压栈;如果优先级低于或等于栈顶的运算符的优先级,则弹栈到后缀表达式中。直到栈顶的运算符的优先级低于外面的运算符优先级或者栈为空时,再把外面的运算符压栈。
(f)中缀表达式读完后,如果运算符栈不为空,则将其内的运算符逐一弹出,输出到后缀表达式中。
(2)然后对后缀表达式进行求值
(a)从头到尾读取表达式,如果遇到数字就压栈。
(b)如果遇到运算符,弹出两个数进行运算,将运算结果压栈。
代码实现:(为了简便起见,没有处理异常;支持负数、小数、多位数运算;支持加减乘数乘方模运算功能)20200202

#include<iostream> #include<stack> #include<queue> #include<string> #include<cstring> #include<cmath> using namespace std; typedef struct{ double num; //操作数 char op; //操作符 bool flag; //true为操作数,false为操作符 }node; stack<char> sign; //临时存放操作符 queue<node> suffix; //存放后缀表达式 stack<double> data; //后缀表达式求值时临时存放操作数 int priority(char ch){ //运算符优先级判断 //《算法笔记》中用map来映射运算符优先级 if(ch == '+'||ch == '-') return 1; if(ch == 'x'||ch == '/'|| ch== '%') return 2; if(ch == '^') return 3; if(ch == '(') return 0; } void aRb(char ch){ //判断运算类型 double a,b; a = data.top(); data.pop(); b = data.top(); data.pop(); //依次取出栈顶两个元素 switch(ch){ case '+' : b += a; break; case '-' : b -= a; break; case 'x' : b *= a; break; case '/' : if(a==0){ throw "Divider can not be zero!"; //除数为0,抛出异常 }else { b /= a; break; } case '%' : if(a==0){ throw "divider can not be zero!"; //除数为0,抛出异常 }else { b = (double)((int)b%(int)a); break; } case '^' : b = pow(b,a); //乘方、开方运算 break; } data.push(b); } int base(char temp){ //判断字符类型 char base_digtal[] = {"1234567890"}; char base_sign[] = {"+-x/%^"}; char base_others[] = {"().="}; if(strchr(base_digtal,temp)) return 1; else if(strchr(base_sign,temp)) return 2; else if(strchr(base_others,temp)) return 3; else return 0; } void change(string temp){ for(int i=0;i<temp.length();){ //从左到右依次读中缀表达式 if(base(temp[i]) == 1 || temp[i] =='.'){ //把数字字符处理成数值 double d = 0; bool state_fraction = 0;//判断当前数字字符在小数点前面还是后面 int count = 1; while(base(temp[i]) == 1 || temp[i] =='.'){ if(!state_fraction&&base(temp[i])==1){ d *= 10; d += (double)(temp[i] - '0'); }else if(temp[i]=='.'){ state_fraction = 1; }else if(state_fraction&&(base(temp[i])==1)){ d += (double)(temp[i] - '0')/pow(10,count); count++; } i++; } node t; t.num = d; t.flag = 1; suffix.push(t); //数值入栈 }else{//处理非数字字符 if((sign.empty()||temp[i] == '(') && temp[i]!='=') sign.push(temp[i]); else if(temp[i] == ')'){ while(!sign.empty() && sign.top()!='('){ node t; t.op = sign.top(); t.flag = 0; suffix.push(t); sign.pop(); } if(!sign.empty()) sign.pop(); //pop操作前都必须判断是否为empty } else if(temp[i] == '='){ while(!sign.empty()){ node t; t.op = sign.top(); t.flag = 0; suffix.push(t); sign.pop(); } } else if(base(temp[i]) == 2){ if(priority(temp[i])<=priority(sign.top())){ while(!sign.empty()&&priority(temp[i])<=priority(sign.top())){ node t; t.op = sign.top(); t.flag = 0; suffix.push(t); sign.pop(); } } sign.push(temp[i]); } i++; } } } double calculate(){ while(!suffix.empty()){ if(suffix.front().flag == 1) { printf("%lf ",suffix.front().num); data.push(suffix.front().num); } else if(suffix.front().flag == 0){ printf("%c ",suffix.front().op); aRb(suffix.front().op); } suffix.pop(); } return data.top(); } int main() { double ans; //最终计算结果 while(1){ string temp; //存储读入的字符串,用于预处理等操作 while(!data.empty()) data.pop(); while(!sign.empty()) sign.pop(); //初始化 getline(cin,temp); if(temp == "exit") break; //程序出口 string::iterator it = temp.end() - 1; if(*it != '=') temp.insert(it+1,'=');//如果用户没有输入'=',在字符串末尾添加'=' for(it = temp.begin();it!=temp.end();it++){ if(*it == ' ') temp.erase(it); //对空格进行处理;注意先检验空格,再进行下一步处理 if(it==temp.begin() && *it=='-') temp.insert(it,'0'); if(*it == '-'){ if(base(*(it-1))!=1 && *(it-1)!=')') temp.insert(it,'0'); //对'-'前没有数字的情况进行加0处理 } } cout<<temp<<endl; change(temp); ans = calculate(); cout<<endl; cout<<"ans = "<<ans<<endl; } return 0; } //测试数据 //-(-3+(3x(5/2+(3^2%5)+5)x2)/3)+((2x5-3)x2)= // - ( - 2 + 3 - 5 ) = //12+.3-.5x2 //.3+4-2x(3+5/2^3)-.3 //-3.4+4 //2.3x(3+3.4)-4/2.2 //(2-(-(-(-(-(-2)x3)x1)x.3)/2.3)-2)/2
算法3:基于二叉树的逆波兰算法 (20200122)
算法描述:将中缀表达式用二叉树结构存储。二叉树的后序遍历即为后缀表达式,然后用逆波兰算法求后缀表达式的值。
如何构建二叉树?(摘自课件)
1. If the current token is a '(', add a new node as the left child of the current node, and descend to(指向) the left child.
2. If the current token is in the vector ['+','-','/','*'], set the root value of the current node to the operator represented by the current token. Add a new node as the right child of the current node and descend to the right child.
3. If the current token is a number, set the root value of the current node to the number and return to the parent.
4. If the current token is a ')', go to the parent of the current node.
关键点提醒:构造二叉树时要注意初始化叶节点的左右孩子为NULL
代码实现:(仅支持一位数加减乘除运算,且必须带有括号;也可对字符串进行预处理成下列代码能运行的格式)

#include<iostream> #include<stack> using namespace std; typedef struct BSTNode{ char value; struct BSTNode *parent; struct BSTNode *left; struct BSTNode *right; }BSTNode; BSTNode *cur = new BSTNode; int ans; stack<char> s; void aRb(char t){ char temp = s.top(); s.pop(); if(t=='+') ans = s.top() + temp; else if(t=='-') ans = s.top() - temp; else if(t=='x') ans = s.top() * temp; else if(t=='/') ans = s.top() / temp; s.pop(); s.push(ans); } void InOrder(BSTNode *T){ if(T!=NULL){ InOrder(T->left); cout<<T->value; InOrder(T->right); } } void PostOrder(BSTNode *T){ if(T!=NULL){ PostOrder(T->left); PostOrder(T->right); cout<<T->value<<" "; if(T->value>='0'&&T->value<='9') s.push(T->value - '0'); else aRb(T->value); } } int main(){ cur->parent = NULL; //赋初值很重要 char ch = cin.get(); while(ch != '\n'){ if(ch=='(') { cur->left = new BSTNode; cur->left->parent = cur; cur = cur->left; cur->left = NULL;//赋初值很重要 cur->right = NULL; } else if(ch>='0'&&ch<='9'){ cur->value = ch; cur = cur->parent; } else if(ch==')'){ if(cur->parent) cur = cur->parent; } else if(ch=='+'||'-'||'x'||'/'){ if(ch=='+') cur->value = '+'; else if(ch=='-') cur->value = '-'; else if(ch=='x') cur->value = 'x'; else if(ch=='/') cur->value = '/'; cur->right = new BSTNode; cur->right->parent = cur; cur = cur->right; cur->left = NULL;//赋初值很重要 cur->right = NULL; } ch = cin.get(); } cout<<"中缀表达式:"; InOrder(cur); cout<<endl; cout<<"后缀表达式:"; PostOrder(cur); cout<<endl; cout<<"Answer = "<<ans<<endl; return 0; } /*----Test---- //注意测试数据需严格按照该标准来定,否则无法构建二叉树 (3+(4x5)) (3-(4+(5x3))) (3-(4-(5x(3-(4+5)))) */
反思:
逆波兰表达式进行一位数运算时,具有很大的优点,但涉及到多位数与小数的运算时,它使用起来需要一点技巧。因为转化为后缀表达式时,需要将数值和运算符存入同一个队列中,而如果是多位数与小数,则必须将数值转化为double型,而不能利用char型(一位数可以利用char型减去'0'的ASCII码),但是运算符是char型,这样就导致了同一个队列中要存入两种类型的元素。
有两种解决办法:第一种:(思路来源于《算法笔记》P249)将队列的基本类型定义为结构体类型;(如上述代码所示)
第二种:把运算符double化,即把运算符转为double型类型的数据与数值一道,存入同一个容器中,为了避免运算符的值与数值的值发生冲突,我们必须对数值的范围作出限制(如若我们规定+为99999999,那么所有进行计算的数值必须小于99999999),这样使得运算范围缩小,故不推荐。