1. 题目来源
链接:3302. 表达式求值
相关题目:
2. 题目解析
本题是中缀表达式求值问题,中缀表达式就是我们自己所书写的表达式。后缀表达式也称逆波兰表达式,也是计算机所识别的表达式。
我们可以将表达式转化为初始的表达式树,这颗表达式树的中序遍历即为中缀表达式,表达式的后序遍历即为后序表达式。
表达式树的内部节点都是运算符,叶节点都是操作数,即数字。
中缀表达式需要括号来确定运算符优先级,而后缀表达式不需要,这也是后缀表达式应用于计算机计算的原因。
中缀表达式问题偏难,思维难度大,也是栈的经典应用。
a*b+c
与 a+b*c
这两个中缀和后缀表达式都很有代表性。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <stack>
#include <unordered_map>
using namespace std;
const int N = 1e5+5;
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; // 可拓展,{'^', 3},在 cal() 函数中加相对应的运算定义即可
stack<char> op; // 经典双栈操作,符号栈、运算符栈
stack<int> num;
void cal() {
int b = num.top(); num.pop(); // 反着写,因为 a/b、a-b 的时候,栈顶先是 b,之后才是 a
int a = num.top(); num.pop();
auto c = op.top(); op.pop();
if (c == '+') num.push(a + b);
else if (c == '-') num.push(a - b);
else if (c == '*') num.push(a * b);
else num.push(a / b);
}
// 整个代码中没有涉及运算符的直接操作,采用 cal() 函数封装的形式完成,可扩展性强
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i ++ ) {
auto c = s[i];
if (c >= '0' && c <= '9') {
int x = 0, j = i;
while (j < s.size() && s[j] >= '0' && s[j] <= '9') x = x * 10 + s[j ++ ] - '0';
i = j - 1; // 别忘记更新 i,跳过这个数字
num.push(x);
} else if (c == '(') op.push(c); // 左括号直接入栈
else if (c == ')') {
while (op.top() != '(') cal(); // 右括号的话,从栈顶开始操作运算符,直到找到左括号为止。从栈顶开始,就是从右向左操作
op.pop(); // 左括号出栈
} else {
while (op.size() && pr[op.top()] >= pr[c]) cal(); // 在本题中,这个while最多执行两次,所以改成两个if也可以过
op.push(c);
}
}
while (op.size()) cal(); // 最后操作剩余的符号
printf("%d\n", num.top());
}
复习:2025年03月07日13:37:02
golang 版本写法:
package main
import "fmt"
var (
pr = map[byte]int{
'+': 1,
'-': 1,
'*': 2,
'/': 2,
}
num, op = []int{}, []byte{}
)
func calc() {
b, a := num[len(num)-1], num[len(num)-2]
num = num[:len(num)-2]
c := op[len(op)-1]
op = op[:len(op)-1]
switch c {
case '+':
num = append(num, a + b)
case '-':
num = append(num, a - b)
case '*':
num = append(num, a * b)
case '/':
num = append(num, a / b)
}
}
func main() {
var s string
fmt.Scanf("%v", &s)
for i := 0; i < len(s); i ++ {
c := s[i]
if c >= '0' && c <= '9' {
j := i
x := 0
for j < len(s) && s[j] >= '0' && s[j] <= '9' {
x = x * 10 + int(s[j] - '0')
j ++
}
i = j - 1
num = append(num, x)
} else if c == '(' {
op = append(op, c)
} else if c == ')' {
for op[len(op) - 1] != '(' {
calc()
}
op = op[:len(op) - 1] // 注意不要遗忘
} else {
for len(op) > 0 && pr[op[len(op) - 1]] >= pr[c] {
calc()
}
op = append(op, c)
}
}
// 注意不要遗忘
for len(op) > 0 {
calc()
}
fmt.Println(num[len(num) - 1])
return
}
由上式代码稍作修改可以快速从中缀表达式转化到后缀表达式。
- 把数字栈直接删掉,遇到数字直接输出,每次操作,取到符号直接输出即可。
所以,上面这个中缀表达式运算过程中就是转化为后缀进行计算的,每次操作就是后缀的操作,拿符号,两个数,计算完再压栈。
等于说是隐式的进行了后缀表达式的操作。
#include <bits/stdc++.h>
using namespace std;
stack<char> op;
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
void eval() {
char c = op.top(); op.pop();
cout << c << ' ';
}
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i ++ ) {
char c = s[i];
if (isdigit(c)) {
int j = i, x = 0;
while (j < s.size() && isdigit(s[j])) x = x * 10 + s[j ++ ] - '0';
i = j - 1;
cout << x << ' ';
}
else if (c == '(') op.push(c);
else if (c == ')') {
while (op.top() != '(') eval();
op.pop();
}
else {
while (op.size() && pr[op.top()] >= pr[c]) eval();
op.push(c);
}
}
while (op.size()) eval();
return 0;
}
复习:2025年03月07日13:46:29
golang 写法:
package main
import "fmt"
var (
pr = map[byte]int{
'+': 1,
'-': 1,
'*': 2,
'/': 2,
}
op = []byte{}
)
func calc() {
c := op[len(op)-1]
op = op[:len(op)-1]
fmt.Printf("%c ", c)
}
func main() {
var s string
fmt.Scanf("%v ", &s)
for i := 0; i < len(s); i ++ {
c := s[i]
if c >= '0' && c <= '9' {
j := i
x := 0
for j < len(s) && s[j] >= '0' && s[j] <= '9' {
x = x * 10 + int(s[j] - '0')
j ++
}
i = j - 1
fmt.Printf("%v ", x)
} else if c == '(' {
op = append(op, c)
} else if c == ')' {
for op[len(op) - 1] != '(' {
calc()
}
op = op[:len(op) - 1] // 注意不要遗忘
} else {
for len(op) > 0 && pr[op[len(op) - 1]] >= pr[c] {
calc()
}
op = append(op, c)
}
}
// 注意不要遗忘
for len(op) > 0 {
calc()
}
return
}