栈的应用2:表达式求值

表达式求值,通常的方法是人为创建两个栈来解决问题,但其实也是可以用递归的方法来解决的。使用递归和双栈(两个栈)的原理和本质都是一样的。递归其实是使用了系统栈,和人为写栈来解决问题是同理的,故两种方法本质是没有区别的。

下面打算先用递归的方式解决表达式求值的问题。

先理解个概念,表达式树:以运算符作为根节点,以相关的操作数作为子节点。


3*(4+5)是一个乘法表达式,4+5这个加法只是在解决乘法问题前的一个子问题。

那么这里其实就是乘法问题里面包含了一个加法的子问题。

根节点代表了3*(4+5)是一个乘法表达式。两棵子树代表是3乘(4+5)。其中右子树4+5又是一颗树。这就是问题的嵌套,适合使用递归或者栈来解决此类问题。

如何判断出来这是一个乘法表达式?

因为3*(4+5)这整个式子,乘号是最后一个被计算的运算符。

最后被计算的运算符,又是整个表达式中优先级最低的那个运算符。

于是我们能得到递归解决表达式求值的方法论:

step1:把整个表达式看成一个函数calculate(),找到其中优先级最低的那个运算符。

step2:以找到的那个优先级最低的运算符为界,将整个表达式一分为二。分成了3和(4+5)

step3:在分别把这两部分(3和4+5)看成一个函数calculate(),然后分别递归求解。


Q:如何找到表达式中优先级最低的那个运算符?

A:设定运算符+-的优先级是1,*/的优先级是2。在括号里面的运算符,都额外加100。

以表达式3*(4+5)为例,*优先级是2,+优先级是101。

3*(4*(5+6)),第一个*优先级2,第二个*优先级102,+优先级201。

代码实现:

int calculate (char*s, int l, int r) { 
//参数一是待计算的表达式(字符串),参数二和三是字符串的坐标,代表待计算的范围。
    //step1:找到优先级最低的那个运算符。
    int op = -1; //op是最低优先级的运算符的坐标,先初始化为-1。
    int pri = 10000-1; //目前最小的优先级是多少。
    int cur_pri; //当前运算符的优先级。
    int temp = 0; //额外由括号增加的优先级。(一层括号加100,两层括号加200)
    for(i = l; i <= r; i++) { //for循环遍历表达式,找出最低优先级的运算符。
        cur_pri = 10000; //假设最开始,当前运算符的优先级是最大。
        switch (s[i]) {
            case '+':
            case '-': cur_pri = 1 + temp; break;
            case '*':
            case '/': cur_pri = 2 + temp; break;
            case '(': temp += 100; break;
            case ')': temp -= 100; break;
        }
        if(cur_pri <= pri) { //当前运算符的优先级和最小的优先级做比较。
            pri = cur_pri;
            op = i;
        }
    }
    if(op == -1) { //递归终止条件
    //找不到最低优先级的运算符,证明当前表达式里面没有运算符,只有数字和括号。
        int num = 0;
        for(int i = l; i <= r; i++) { //将只包含数字和括号的表达式(字符串)转换成纯数字(int整数)。
            if(s[i] < '0' || s[i] > '9') continue; //筛掉表达式里的括号(字符),只保留数字(字符)。
            else num = num*10 + s[i]-'0'; //将数字字符转换成真正的数字,并将其加入到num里。
            
        }
        return num;
    }
    printf("calculate find: %c\n", s[op]); //输出,看一下这一轮找到的优先级最低的运算符是啥。
    //step2:递归运算
    int a = calculate(s, l, op-1);
    //以op为分界点,将表达式分成两半,来进行递归运算。(不包括op本身)
    int b = calculate(s, op+1, r);
    //这里就已经得到了运算符op的左表达式的值a和右表达式的值b。
    switch (s[op]) { //根据op具体是什么运算符,返回对a和b计算得到的表达式的值。
        case '+': return a+b;
        case '-': return a-b;
        case '*': return a*b;
        case '/': return a/b;
    }
    return 0;
}

int main() {
    char s[100]; //字符串存储表达式
    while (~scanf("%[^\n]s", s)) { //读入一个表达式(字符串)
        gethchar();
        printf("%s = %d\n", s, calculate(s, 0, strlen() - 1));
    }

    return 0;
}

递归代码解析:

33    //step2:递归运算
34    int a = calculate(s, l, op-1);
36    int b = calculate(s, op+1, r);

最低优先级的运算符op将表达式一分为二,然后将左子式(L~op-1)和右子式(op+1~R)都看成一整个表达式,再分别递归求值(参照下面图解)。

递归代码图解:把递归想成表达式树即可。

        


以表达式3*(4+(5-2))为例,其递归过程如下面图解。

表达式会一直递归,直到分解出来的子表达式里面没有运算符。(递归终止条件)

第一轮递归,把表达式3*(4+(5-2))拆分成了运算符*,左子式3,右子式4+(5-2)。

第二轮递归,左子式3不需要再拆分,把右子式拆分成4和5-2。

第三轮递归,左子式4不需要拆分,右子式5-2拆分成5和2。

表达式已经没有可以再拆分的了,满足了递归终止条件,递归完毕。

特殊情况:若表达式里面有负数(如:5+-3)该怎么计算?

思路:在表达式里面的负数(-3)前面加上一个0。表达式5+-3变成5+0-3,再正常计算即可。

上面用递归的方式实现了表达式求值,下面要用栈的方式来实现此功能。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值