表达式求值问题的实现

         这是C++算法基础-数据结构专栏的第二十二篇文章,专栏详情请见此处


引入

        相信大家都会做如1+(2+3)*4/5-6这样的算式,而我们今天要学习让计算机通过表达式求它的值,这就是表达式求值问题。

        下面我们就来讲表达式求值问题的实现。

定义

        表达式求值问题要解决的问题一般是输入一个字符串表示的表达式,要求输出它的值。表达式包含两类字符:运算数和运算符。

前置过程

        在本文的学习中,你可能会在一些小节中有不懂的地方,遇到这种情况时,你应该将整篇文章阅读完,理解整体思路,再回来再看不懂的地方,此时你一定会豁然开朗(*^▽^*)

        使用数据结构:栈

        在运算的时候,需要考虑运算符优先级问题,若优先级低的数据在优先级高的数据前面,先要算完优先级高的数据再倒序回来算优先级低的数据,所以选择了这个数据结构。还有就是表达式中有运算符和数字两个对象,所以要用两个栈来存储。

        代码如下:

stack<int>num;
stack<char>op;

        在对字符串遍历之前,我们还要再干两件事。

        哈希表判断优先级

        第一就是建立一个哈希表进行映射来判断运算优先级

        代码如下:

unordered_map<char,int>pr{{'+',1},{'-',1},{'*',2},{'/',2}};
        计算函数

        第二是一个计算函数,先从数字栈取出(取出实际上是两个操作:取(取栈顶值)和出(弹出数据))两个数,符号栈中取出一个符号,再根据符号进行计算。此处有一点需要注意,由于栈先进后出的特性,先进去的数字a在后进去的数字b下面,所以先取出的应该是后进去的数字b,再取出的才是先进去的数字a。当计算完成后,还要把计算的结果向栈顶插入,以便进行下次的计算。

        代码如下:

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:一次性提取完。具体来说,为了不对变量i造成影响,我们用一个新的变量j进行遍历;在循环中,若j在字符串里(防止该数字是字符串中最后一个数字而造成死循环)且当前字符是数字,就将当前正在处理的数据x10并加上str[j]-'0',意思就是将x的所有数位向左移动一位,再将个位赋值为str[j]用ASCII码进行偏移操作后的量(就是将此字符减去‘0’),把j++,就这样不断循环,直到数字提取完成;最后,i赋值为j-1,因为每轮循环有i++,所以我们需要倒指向最后一个数字。

注: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. 前一个运算符比当前的运算符的优先级要高;
  2. 前一个运算符比当前的运算符的优先级要低;
  3. 前一个运算符和当前的运算符的优先级相等。

        由于先算优先级高的运算,所以关系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++算法基础专栏文章    下一篇-队列的实现


每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容

点个赞,关注一下呗~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值