问题描述
小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
分析
算法
解决算术表达式类题目的核心算法是
- 创建两个栈
operands和operationsoperands用于记录操作数及运算结果operations用于记录运算符
- 从头开始遍历表达式。对于表达式中的每个元素
ch, 如果ch为数,那么将ch入栈operands; 否则(ch为运算符):- 如果
operations为空,那么将ch入栈operations;否则(operations不空),将operations的栈顶top与ch进行运算优先级比较:- 若
top高于ch(如乘法高于加法),则将top运算应用于operands;从operands的栈顶开始,operands的第个元素就是top运算的倒数第个操作数(设top是元运算,则;本题涉及的运算——加减乘除——都是二元运算),top运算和这些操作数都要出栈。top运算完成后,将其结果入栈operands。返回上一步,重新检查operations。 - 若
top等价于ch(这种情况只用于括号匹配——top为(而ch为)),则弹出top,丢弃ch(因此)不可能入栈operations)。 - 若
top低于ch(如加法低于乘法),则将ch入栈operations。
- 若
- 如果
- 如果表达式遍历完成后,
operations不空,则依次弹出operations的栈顶top,将top的运算应用于operands(类似上文情况1的做法)。 - 最终:
operations应当为空。operands只剩下一个元素;这个元素就是运算结果。
上述算法具有时间复杂度,对应题解代码的第51至75行。
运算符
表示
我们使用枚举Operation表示加减乘除和左右括号,亦即使用自然数(而非原始的字符)表示运算符。
这种做法具有以下好处:
- 运算符可以直接作为运算符优先级比较表(见题解代码第19行的
compare)的索引; - 加减乘除(即0123)可以通过数组(见题解代码第31行的
fn)直接对应到其函数实现,无需使用条件判断语句或std::map等映射容器,从而让代码更为紧凑。
优先级
运算符之间的优先级比较发生在operations的栈顶top与当前遇到的运算符curr之间。
比较结果有三种可能:(lt)、(eq)和(gt)。
比较结果可用一个矩阵来表示:
+ | - | * | / | ( | ) | |
|---|---|---|---|---|---|---|
+ | ||||||
- | ||||||
* | ||||||
/ | ||||||
( |
若栈顶是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;否则:- 若
ch为bi,则- 如果
unary为true,那么bi是单目运算,否则bi是双目运算。
- 如果
- 并且,若
ch不为),则unary = true。
- 若