大家在小学的时候肯定都做过这样的加减乘除的题目,比如: 3 * (4 + 5)。还记得我们当时的数学老师怎么教的吗?是不是说要先算括号里面的值,然后再计算括号外面的值。作为一名程序员,我们能否通过我们的程序来算出这个表达式的结果的呢?我们可以通过递归来实现表达式求值。
递归实现表达式求值
在说递归实现表达式求值之前,我们首先介绍一下栈,我们知道栈是先入后出,就像我们在调用函数的时候,js引擎会创建一个调用栈(想了解执行上下文可以移步至此)。所以栈是可以处理具有完全包含关系的问题。那我们想想递归是不是也是利用栈呢?
表达式树
我们可以用树来表示表达式。通常情况下,表达式树以运算符作为根节点,以相关的操作数作为子节点。现在问大家一个问题,上面的这个表达式是一个乘法表达式,还是一个加法表达式?其实这是一个乘法表达式,因为乘号是这个表达式中最后一个被计算的运算符,所以也是整个表达式中优先级最低的运算符。从树中可以看到,根节点表示的运算符最低。所以有了优先级这个概念,就可以推导出来递归实现表达式求值。
表达式中的优先级
假设我们有一个表达式求值的函数calc,我们把表达式传入calc函数中,然后找到优先级最低的运算符的位置。找到之后将整个表达式拆成两半,然后递归的调用calc
如果我们想用递归的方式显示表达式,我们先要给每个运算法定义优先级大小的值:
+,-优先级为 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额外由括号增加的优先级。
首先从头到尾遍历整个表达式,把每个运算符对用的优先级计算出来。
然后将优先级最小的运算符赋值给pri,将优先级最小的运算符的索引赋值给op。如果op为-1说明没有运算符就是一个数字,直接返回即可。如果不为-1,则从优先级最小的运算符的位置拆分左右两份递归执行。然后将递归的返回结果按照运算符进行计算即可。
总结
这篇文章利用递归的方式起来解决表达式求值,实际的思想其实是栈。主要是为了说明栈是可以处理具有完全包含关系的问题
欢迎大家点赞评论,大家一起学习一起进步!!!