问题描述
小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
解题思路
根据题目可以知道输入的表达式是一个中缀表达式,可以使用运算符优先级的处理以及双栈(操作数栈和运算符栈)实现表达式的计算。 核心思路是:
-
遍历表达式:
- 遇到数字:直接压入操作数栈。
- 遇到左括号:压入运算符栈。
- 遇到右括号:弹出运算符栈直到左括号,逐步计算结果。
- 遇到运算符:根据优先级判断是否需要弹栈并计算,最后压入当前运算符。
-
优先级规则:
- 运算符优先级
*和/高于+和-。 - 遇到括号时,确保括号内的表达式先被计算。
- 运算符优先级
-
最后清理:遍历完成后,清空运算符栈并完成计算。
代码
#include <cctype>
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
using namespace std;
const int N = 100010;
int num[N], op[N], ttn, tto;
void eval() {
int b = num[ttn--];
int a = num[ttn--];
char c = op[tto--];
int x;
if(c == '+')
x = a + b;
else if(c == '-')
x = a - b;
else if(c == '*')
x = a * b;
else
x = a / b;
num[++ttn] = x;
}
int solution(std::string expression) {
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
for(int i = 0; i < expression.size(); i++) {
char c = expression[i];
if(isdigit(c)) {
int x = c - '0';
num[++ttn] = x;
} else if(c == '(') {
op[++tto] = c;
} else if(c == ')') {
while(op[tto] != '(')
eval();
tto--;
} else {
while(tto != 0 && op[tto] != '(' && pr[op[tto]] >= pr[c])
eval();
op[++tto] = c;
}
}
while(tto != 0)
eval();
return num[ttn];
}
int main() {
// You can add more test cases here
std::cout << (solution("1+1") == 2) << std::endl;
std::cout << (solution("3+4*5/(3+2)") == 7) << std::endl;
std::cout << (solution("4+2*5-2/1") == 12) << std::endl;
std::cout << (solution("(1+(4+5+2)-3)+(6+8)") == 23) << std::endl;
return 0;
}
关键代码解释
1. 运算符优先级设置
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
unordered_map用于记录运算符的优先级。- 数值越高表示优先级越高。
- 加和减的优先级是1,乘和除的优先级是2
2. 数字处理
if(isdigit(c)) {
int x = c - '0';
num[++ttn] = x;
}
- 检测到当前字符是数字,将其转换为整数并压入操作数栈
num。
3. 括号处理
- 左括号:简单压栈。
else if(c == '(') {
op[++tto] = c;
}
- 右括号:弹出运算符栈,逐步计算直到遇到左括号。
else if(c == ')') {
while(op[tto] != '(')
eval();
tto--;
}
4. 运算符处理
- 比较当前运算符和栈顶运算符的优先级,决定是否先计算栈中的运算。
while(tto != 0 && op[tto] != '(' && pr[op[tto]] >= pr[c])
eval();
op[++tto] = c;
5. 计算逻辑
void eval() {
int b = num[ttn--];
int a = num[ttn--];
char c = op[tto--];
int x;
if(c == '+') x = a + b;
else if(c == '-') x = a - b;
else if(c == '*') x = a * b;
else x = a / b;
num[++ttn] = x;
}
- 从操作数栈中弹出两个操作数
a和b,从运算符栈中弹出一个运算符c,根据运算符执行计算并将结果压入操作数栈。
时间空间复杂度分析
时间复杂度
-
表达式遍历:
- 每个字符被访问一次,总复杂度为 O(n)。
-
操作栈处理:
- 每次入栈和出栈操作为 O(1)。
eval函数执行 O(1) 操作,最坏情况下调用 O(n) 次。
-
总时间复杂度为 O(n) 。
空间复杂度
-
主要存储了两个栈:
- 操作数栈
num:大小为 O(n)。 - 运算符栈
op:大小为 O(n)。
- 操作数栈
-
总空间复杂度为 O(n) 。
优化点
- 支持多位数处理
当前代码支持的是个位数的计算,如果是多位数,可以使用一个while循环来提取数值
if(isdigit(c)) {
int x = 0;
while(i < expression.size() && isdigit(expression[i])) {
x = x * 10 + (expression[i] - '0');
i++;
}
i--; // 回退1位,因为for循环还会执行i++
num[++ttn] = x;
}
- 支持空格处理
- 可以添加对空格的跳过逻辑:
if (isspace(c)) continue;
总结
这是一道简单的表达式求值题,可以使用自定义栈来进行解题。个人感觉自定义栈挺方便的,也可以做到一些c++库里的栈做不到的事,可以根据实际需求优化存储方式和操作逻辑,从而减少不必要的开销。