小学一年级的算术题,试试用递归怎么搞

744 阅读3分钟

大家在小学的时候肯定都做过这样的加减乘除的题目,比如: 3 * (4 + 5)。还记得我们当时的数学老师怎么教的吗?是不是说要先算括号里面的值,然后再计算括号外面的值。作为一名程序员,我们能否通过我们的程序来算出这个表达式的结果的呢?我们可以通过递归来实现表达式求值。

递归实现表达式求值

在说递归实现表达式求值之前,我们首先介绍一下,我们知道栈是先入后出,就像我们在调用函数的时候,js引擎会创建一个调用栈(想了解执行上下文可以移步至此)。所以栈是可以处理具有完全包含关系的问题。那我们想想递归是不是也是利用栈呢?

表达式树

image.png

我们可以用树来表示表达式。通常情况下,表达式树以运算符作为根节点,以相关的操作数作为子节点。现在问大家一个问题,上面的这个表达式是一个乘法表达式,还是一个加法表达式?其实这是一个乘法表达式,因为乘号是这个表达式中最后一个被计算的运算符,所以也是整个表达式中优先级最低的运算符。从树中可以看到,根节点表示的运算符最低。所以有了优先级这个概念,就可以推导出来递归实现表达式求值。

表达式中的优先级

假设我们有一个表达式求值的函数calc,我们把表达式传入calc函数中,然后找到优先级最低的运算符的位置。找到之后将整个表达式拆成两半,然后递归的调用calc

image.png

如果我们想用递归的方式显示表达式,我们先要给每个运算法定义优先级大小的值:

  • +,- 优先级为 1
  • *,/ 优先级为 2
  • ()里面的优先级 + 100

代码实现

思路已经理顺了,下面就可以动手写代码吧

function calc(s, l, r) {
  let op = -1; // 最低优先级运算符的位置
  let pri = 10000 - 1; // 中间运算符的优先级
  let cur_pri = 0; // 当前运算符的优先级
  let temp = 0; // 额外由括号增加的优先级

  for (let i = l; i <= r; i++) {
    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) {
    return s;
  }
  let a = calc(s, l, op - 1);
  let b = calc(s, op + 1, r);
  switch (s[op]) {
    case "+":
      return a + b;
    case "-":
      return a - b;
    case "*":
      return a * b;
    case "/":
      return a / b;
  }
}

op表示最低优先级运算符的位置,pri表示中间运算符的优先级的标准,cur_pri表示当前运算符的优先级。temp额外由括号增加的优先级。

首先从头到尾遍历整个表达式,把每个运算符对用的优先级计算出来。

image.png

然后将优先级最小的运算符赋值给pri,将优先级最小的运算符的索引赋值给op。如果op为-1说明没有运算符就是一个数字,直接返回即可。如果不为-1,则从优先级最小的运算符的位置拆分左右两份递归执行。然后将递归的返回结果按照运算符进行计算即可。

总结

这篇文章利用递归的方式起来解决表达式求值,实际的思想其实是栈。主要是为了说明栈是可以处理具有完全包含关系的问题

欢迎大家点赞评论,大家一起学习一起进步!!!