这是C++算法基础-数据结构专栏的第二十二篇文章,专栏详情请见此处。
引入
相信大家都会做如1+(2+3)*4/5-6这样的算式,而我们今天要学习让计算机通过表达式求它的值,这就是表达式求值问题。
下面我们就来讲表达式求值问题的实现。
定义
表达式求值问题要解决的问题一般是输入一个字符串表示的表达式,要求输出它的值。表达式包含两类字符:运算数和运算符。
前置过程
在本文的学习中,你可能会在一些小节中有不懂的地方,遇到这种情况时,你应该将整篇文章阅读完,理解整体思路,再回来再看不懂的地方,此时你一定会豁然开朗(*^▽^*)
使用数据结构:栈
在运算的时候,需要考虑运算符优先级问题,若优先级低的数据在优先级高的数据前面,先要算完优先级高的数据再倒序回来算优先级低的数据,所以选择了栈这个数据结构。还有就是表达式中有运算符和数字两个对象,所以要用两个栈来存储。
代码如下:
stack<int>num;
stack<char>op;
在对字符串遍历之前,我们还要再干两件事。
哈希表判断优先级
第一就是建立一个哈希表进行映射来判断运算优先级。
代码如下:
unordered_map<char,int>pr{{'+',1},{'-',1},{'*',2},{'/',2}};
计算函数
第二是一个计算函数,先从数字栈取出(取出实际上是两个操作:取(取栈顶值)和出(弹出数据))两个数,符号栈中取出一个符号,再根据符号进行计算。此处有一点需要注意,由于栈先进后出的特性,先进去的数字在后进去的数字下面,所以先取出的应该是后进去的数字,再取出的才是先进去的数字。当计算完成后,还要把计算的结果向栈顶插入,以便进行下次的计算。
代码如下:
void eval(){
int b=num.top();
num.pop();
int a=num.top();
num.pop();
char c=op.top();
op.pop();
int x;
if(c=='+')
x=a+b;
else if(c=='-')
x=a-b;
else if(c=='*')
x=a*b;
else
x=a/b;
num.push(x);
}
主体过程
今天所学的表达式求值中,表达式的运算符仅包含+,-,*,/(加 减 乘 整除(整除是向0取整)),可能包含括号,保证给定的表达式合法,所有数字均为正整数。
围绕上述表达式的性质,我们可以看出,表达式中无非就是四种可能的情况:数字,左括号,右括号,加减乘除四种运算符号。所以,我们在进行对字符串的遍历时,分情况去考虑。
数字
数字并不会产生运算过程,只需提取数字,将数字插入数字栈。
Q:提取数字的方法是什么呢?
A:一次性提取完。具体来说,为了不对变量造成影响,我们用一个新的变量进行遍历;在循环中,若在字符串里(防止该数字是字符串中最后一个数字而造成死循环)且当前字符是数字,就将当前正在处理的数据乘并加上,意思就是将的所有数位向左移动一位,再将个位赋值为用ASCII码进行偏移操作后的量(就是将此字符减去‘0’),把,就这样不断循环,直到数字提取完成;最后,将赋值为,因为每轮循环有,所以我们需要倒指向最后一个数字。
注:isdigit()是一个库函数,在头文件<cctype>中,作用是判断某一字符是否为数字字符。
代码如下:
if(isdigit(c)){
int x=0,j=i;
while(j<str.size()&&isdigit(str[j]))
x=x*10+str[j++]-'0';
i=j-1;
num.push(x);
}
左括号
遇到左括号说明会往下走,所以只需将左括号插入符号栈。
代码如下:
else if(c=='(')
op.push(c);
右括号
遇到右括号说明会往上走,所以要逆向运算直至遇到左括号。
代码如下:
else if(c==')'){
while(op.top()!='(')
eval();
op.pop();
}
运算符号
遇到运算符号不意味着立即运算,而是标志着将之前入栈的算式进行运算,具体来说,前一个运算符和当前的运算符无非就是三种关系:
- 前一个运算符比当前的运算符的优先级要高;
- 前一个运算符比当前的运算符的优先级要低;
- 前一个运算符和当前的运算符的优先级相等。
由于先算优先级高的运算,所以关系1可以往前运算;又由于同级运算从左向右算,所以关系3也可以往前运算;只有关系2不可以往前运算,所以说,若符号栈不为空,只要前一个运算符的优先级大于等于当前的运算符的优先级,就可以往前运算。最后,将当前的运算符插入符号栈。
代码如下:
else{
while(op.size()&&pr[op.top()]>=pr[c])
eval();
op.push(c);
}
运算剩余算式
最后,如果若符号栈不为空,就循环运算,直到数字栈内只有一个数字,将它输出。
代码
下面给出表达式求值问题的实现代码:
stack<int>num;
stack<char>op;
void eval(){
int b=num.top();
num.pop();
int a=num.top();
num.pop();
char c=op.top();
op.pop();
int x;
if(c=='+')
x=a+b;
else if(c=='-')
x=a-b;
else if(c=='*')
x=a*b;
else
x=a/b;
num.push(x);
}
int main(){
unordered_map<char,int>pr{{'+',1},{'-',1},{'*',2},{'/',2}};
string str;
cin>>str;
for(int i=0;i<str.size();i++){
char c=str[i];
if(isdigit(c)){
int x=0,j=i;
while(j<str.size()&&isdigit(str[j]))
x=x*10+str[j++]-'0';
i=j-1;
num.push(x);
}
else if(c=='(')
op.push(c);
else if(c==')'){
while(op.top()!='(')
eval();
op.pop();
}
else{
while(op.size()&&pr[op.top()]>=pr[c])
eval();
op.push(c);
}
}
while(op.size())
eval();
cout<<num.top();
return 0;
}
上一篇-栈的实现 C++算法基础专栏文章 下一篇-队列的实现
每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容
点个赞,关注一下呗~