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

123 阅读4分钟

问题描述

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

测试样例

样例1:

输入:expression = "1+1"
输出:2

样例2:

输入:expression = "3+4*5/(3+2)"
输出:7

样例3:

输入:expression = "4+2*5-2/1"
输出:12

样例4:

输入:expression = "(1+(4+5+2)-3)+(6+8)"
输出:23

样例5:

输入:expression = "2*(5+5*2)/3+(6+8*3)"
输出:40

分析

算法

解决算术表达式类题目的核心算法是

  • 创建两个栈operandsoperations
    • operands用于记录操作数及运算结果
    • operations用于记录运算符
  • 从头开始遍历表达式。对于表达式中的每个元素ch, 如果ch为数,那么将ch入栈operands; 否则(ch为运算符):
    • 如果operations为空,那么将ch入栈operations;否则(operations不空),将operations的栈顶topch进行运算优先级比较:
      1. top高于ch(如乘法高于加法),则将top运算应用于operands;从operands的栈顶开始,operands的第  i  \;i\;个元素就是top运算的倒数第  i  \;i\;个操作数(设top  k  \;k\;元运算,则1ik1\leq i\leq k;本题涉及的运算——加减乘除——都是二元运算),top运算和这些操作数都要出栈。top运算完成后,将其结果入栈operands。返回上一步,重新检查operations
      2. top等价于ch(这种情况只用于括号匹配——top(ch)),则弹出top,丢弃ch(因此)不可能入栈operations)。
      3. top低于ch(如加法低于乘法),则将ch入栈operations
  • 如果表达式遍历完成后,operations不空,则依次弹出operations的栈顶top,将top的运算应用于operands(类似上文情况1的做法)。
  • 最终:operations应当为空。operands只剩下一个元素;这个元素就是运算结果。

上述算法具有时间复杂度O(n)O(n),对应题解代码的第51至75行。

运算符

表示

我们使用枚举Operation表示加减乘除和左右括号,亦即使用自然数(而非原始的字符)表示运算符。 这种做法具有以下好处:

  • 运算符可以直接作为运算符优先级比较表(见题解代码第19行的compare)的索引;
  • 加减乘除(即0123)可以通过数组(见题解代码第31行的fn)直接对应到其函数实现,无需使用条件判断语句或std::map等映射容器,从而让代码更为紧凑。

优先级

运算符之间的优先级比较发生在operations的栈顶top与当前遇到的运算符curr之间。 比较结果有三种可能:\preclt)、\equiveq)和\succgt)。 比较结果可用一个矩阵来表示:

+-*/()
+\succ\succ\prec\prec\prec\succ
-\succ\succ\prec\prec\prec\succ
*\succ\succ\succ\succ\prec\succ
/\succ\succ\succ\succ\prec\succ
(\prec\prec\prec\prec\prec\equiv

若栈顶是top,当前遇到的运算符是curr,则矩阵的第top行第curr列就是它们的比较结果。 因为)不可能入栈operations,所以矩阵少了一行。 我们使用一个二维数组compare(见题解代码第19行)记录上述信息。

题解

#include <cctype>
#include <functional>
#include <stack>
#include <string>
#include <vector>
using namespace std;

enum Operation { ADD,
    SUB,
    MUL,
    DIV,
    CUP,
    CAP
};
enum Relation { lt,
    eq,
    gt
};
const Relation compare[5][6] {
    { gt, gt, lt, lt, lt, gt },
    { gt, gt, lt, lt, lt, gt },
    { gt, gt, gt, gt, lt, gt },
    { gt, gt, gt, gt, lt, gt },
    { lt, lt, lt, lt, lt, eq },
};

int solution(const string& expr)
{
    vector<int> operands;
    stack<int> operations;
    function<int(int, int)> fn[4] = { plus<int>(), minus<int>(), multiplies<int>(), divides<int>() };
    auto apply = [&fn, &operands](int operation) {
        int b = operands.back();
        operands.pop_back();
        int a = operands.back();
        operands.back() = fn[operation](a, b);
    };
    auto translate = [](char ch) {
        switch (ch) {
        case '+':
            return ADD;
        case '-':
            return SUB;
        case '*':
            return MUL;
        case '/':
            return DIV;
        }
        return Operation(ch - '(' + CUP);
    };
    for (auto&& ch : expr)
        if (ispunct(ch)) {
            const int op = translate(ch);
        AGAIN:
            if (operations.size())
                switch (compare[operations.top()][op]) {
                case gt:
                    apply(operations.top());
                    operations.pop();
                    goto AGAIN;
                case eq:
                    operations.pop();
                    break;
                case lt:
                    operations.push(op);
                }
            else
                operations.push(op);
        } else
            operands.push_back(ch - '0');
    while (operations.size()) {
        apply(operations.top());
        operations.pop();
    }
    return operands.back();
}

拓展

如果表达式中有既可作为双目运算符又可作为单目运算符的(如取相反数运算“-”)bi,那么

  • 它具有比加减乘除更高的优先级
  • 创建一个初始为true标记量unary;在算法的每次循环中, 如果ch为数,那么unary = false;否则:
    • chbi,则
      • 如果unarytrue,那么bi是单目运算,否则bi是双目运算。
    • 并且,若ch不为),则unary = true