简单四则运算解析器 | 豆包MarsCode AI刷题

295 阅读4分钟

一. 问题描述

小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符+-及括号()。注意,字符串中不包含空格。除法运算应只保留整数结果。请实现一个解析器计算这些表达式的值,且不使用任何内置的eval函数。

我们先来思考问题要求,输入的是一个包含加减乘除和括号的字符串表达式,而且表达式中的数字是非负整数且小于10,所以主要解决的问题是处理括号优先级和运算符的优先级。

二. 数据结构

数据结构的选择:

  • 使用栈(Stack)来处理括号和运算符的优先级。
  • 使用两个栈:一个用于存储操作数(数字),另一个用于存储运算符。

栈是一种非常常见的数据结构,它遵循着特定的访问规则,即后进先出。举个例子:盘子是逐个堆放的,每次取盘子时,你只能从最上面拿走一个。这就像栈的操作,最后放上去的盘子(栈顶元素)是最先被取走的。 因为栈的后进先出的特点,所以非常适合用来解决计算数字表达式等问题。

三. 算法步骤

  1. 初始化栈:创建两个栈,一个存放数字,另一个存放运算符。
  2. 遍历表达式:从左到右遍历字符串表达式。
  • 如果遇到数字,直接压入数字栈。
  • 如果遇到左括号 (,压入运算符栈。
  • 如果遇到右括号 ),弹出运算符栈中的运算符,直到遇到左括号 (,并进行相应的计算。
  • 如果遇到运算符(+-*/),比较当前运算符与栈顶运算符的优先级:(如果当前运算符优先级低于或等于栈顶运算符,先弹出栈顶运算符进行计算,然后将当前运算符压入栈。否则,直接将当前运算符压入栈。)

比如:当/进栈时,/优先级与*相同

a824cf033451ec84e94c28de522972e.png

完整js代码如下;

    function solution(expression) {
      let nums = [];
      let ops = [];

      for (let i = 0; i < expression.length; i++) {
        let c = expression.charAt(i);
        if (!isNaN(c)) { // 如果是数字
          nums.push(parseInt(c, 10));
        } else if (c === '+' || c === '-' || c === '*' || c === '/') {
          //  当前操作符c的优先级小于等于栈顶的操作符优先级
          while (ops.length > 0 && precedence(ops[ops.length - 1]) >= precedence(c)) {
            //当碰上优先级更小或相等就从数字栈顶取俩个数,符号栈栈顶元素
            //运算式 : 后取出的数字  符号  先取的数字
            //放入num栈中
            nums.push(applyOp(ops.pop(), nums.pop(), nums.pop()));
          }
          ops.push(c);
        } else if (c === '(') {
          ops.push(c);
        } else if (c === ')') {
          //碰上右括号就计算
          while (ops[ops.length - 1] !== '(') {
            nums.push(applyOp(ops.pop(), nums.pop(), nums.pop()));
          }
          ops.pop();
        }
      }

      while (ops.length > 0) {
        nums.push(applyOp(ops.pop(), nums.pop(), nums.pop()));
      }

      return nums.pop();
    }
    // 判断运算符优先级
    function precedence(op) {
      if (op === '+' || op === '-') {
        return 1;
      } else if (op === '*' || op === '/') {
        return 2;
      }
      return 0;
    }

    // 表达式计算
    function applyOp(op, b, a) {
      switch (op) {
        case '+':
          return a + b;
        case '-':
          return a - b;
        case '*':
          return a * b;
        case '/':
          return Math.floor(a / b); // JavaScript中除法会返回浮点数,使用Math.floor确保返回整数
      }
      return 0;
    }

    // 测试代码
    console.log(solution("1+1") === 2);                      //true
    console.log(solution("3+4*5/(3+2)") === 7);              //true
    console.log(solution("4+2*5-2/1") === 12);               //true
    console.log(solution("(1+(4+5+2)-3)+(6+8)") === 23);     //true

3. 计算结果:遍历结束后,如果运算符栈中还有运算符,依次弹出并进行计算,直到运算符栈为空。操作数栈中剩下的唯一元素即为表达式的计算结果。

四. 个人总结

栈是一种后进先出的数据结构,它非常适合用来计算数学表达式。

  1. 在数学表达式中,运算符之间有规则,也就是会先乘除后加减,栈可以帮助我们按照正确的顺序执行这些操作。当一个操作符入栈时,我们可以比较它与栈顶操作符的优先级,如果栈顶操作符的优先级更高或相等,我们就先执行栈顶的操作符。
  2. 处理包含括号的表达式时,栈可以用来放左括号,当遇到右括号时,就可以执行括号内的所有操作,直到遇到与之匹配的左括号。这符合栈的后进先出的特性。
  3. 存储顺序不会改变,栈提供了一种特定的存储数字和操作符的方式,当用到它们时它们的顺序就不会被打乱。
  4. 许多数学表达式具有递归结构,即表达式可以包含其他表达式。栈的先进后出特性使得递归处理变得简单,因为我们可以将子表达式的结果先计算出来,然后使用这些结果来计算更外层的表达式。

综上,栈因其结构简单且符合数学表达式计算的特性,使用栈来计算表达式可以简化算法的实现。我们只需要遍历表达式一次,就可以在遍历过程中处理所有的操作符和操作数。