用调度场算法把中缀算式转成后缀表达式再求值,一次线性扫描即可处理括号与全部运算符
OJ: roj
题目 ID: 1356
难度:普及-
标签:字符串栈表达式求值
创建: 2026-09-30 06:51
更新: 2026-10-07 13:50
前置题目
题目描述
给定一个中缀算式字符串,只包含括号 ( )、数字 0-9、双目运算符 + - * / ^,求算式的值。/ 为整数除法(商向零截断),^ 为乘方,括号保证配对且算式合法。
输入一行算式,输出一行整数结果。
样例:输入 1+(3+2)*(7^2+6*9)/(2),输出 258。
思路
用调度场算法把中缀式转成后缀表达式:数字直接输出,遇到 ( 入栈,) 则弹出到 ( 为止,普通运算符先弹出栈里优先级不低于它的运算符再入栈。最后用另一个栈对后缀表达式求值,数字入栈,运算符弹出栈顶两个数计算后压回。
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 06:52
# update_at: 2026-09-30 06:52
import sys
from collections.abc import Iterator
DIGITS = '0123456789'
BRACKETS = '()' # 括号只影响算子的进出栈时机,本身不参与数值计算
PRECEDENCE = {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3} # 数值越大结合得越紧
def trunc_div(left: int, right: int) -> int:
"""整数除法向零截断:题面的 "/" 是普通整除,负商不向下取整。"""
quotient = abs(left) // abs(right)
return -quotient if (left < 0) != (right < 0) else quotient
BINARY = {
'+': lambda x, y: x + y, # 加法
'-': lambda x, y: x - y, # 减法
'*': lambda x, y: x * y, # 乘法
'/': trunc_div, # 整除
'^': lambda x, y: x ** y, # 幂
}
def tokens(expr: str) -> Iterator[int | str]:
"""把算式切成整数与单字符算子:整数是极长数字段,其余无关字符(换行、空格)直接丢弃。"""
number = 0
has_digit = False
for char in expr:
if char in DIGITS:
number = number * 10 + int(char)
has_digit = True
continue
if has_digit:
yield number # 数字段到此结束,先结算它再处理当前算符
number, has_digit = 0, False
if char in PRECEDENCE or char in BRACKETS:
yield char
if has_digit:
yield number
def to_postfix(items: Iterator[int | str]) -> list[int | str]:
"""调度场算法:中缀转后缀(逆波兰式),把"谁先算"一次性编码进序列顺序。"""
output: list[int | str] = []
ops: list[str] = []
for item in items:
if isinstance(item, int):
output.append(item)
elif item == '(':
ops.append(item)
elif item == ')':
while ops[-1] != '(': # 括号内的部分已经完整,全部归约出去
output.append(ops.pop())
ops.pop() # 栈顶此时必是配对的 '(',它本身不进后缀串
else:
while ops and ops[-1] != '(' and PRECEDENCE[ops[-1]] >= PRECEDENCE[item]:
output.append(ops.pop()) # 先算左面更高或同级的算子,同级按从左到右
ops.append(item)
output += reversed(ops) # 栈底到栈顶依次落到后缀串末尾
return output
def evaluate(postfix: list[int | str]) -> int:
"""后缀求值:数字入栈,算子弹出右侧两个数,用 BINARY 合并后压回。"""
stack: list[int] = []
for item in postfix:
if isinstance(item, int):
stack.append(item)
else:
right, left = stack.pop(), stack.pop()
stack.append(BINARY[item](left, right))
return stack[0]
def solve() -> None:
expr = sys.stdin.buffer.read().decode(errors='ignore') # 数据是 CRLF,换行由 tokens 丢弃
print(evaluate(to_postfix(tokens(expr))))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 11:52
* update_at: 2026-10-05 11:52
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
typedef long long ll;
char expr[1000005]; // 输入算式
vector<char> ops; // 运算符栈
vector<ll> nums; // 后缀求值栈
// 运算符优先级
ll precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
if (op == '^') return 3;
return 0;
}
// 整数除法,商向零截断
ll trunc_div(ll left, ll right) {
ll quotient = abs(left) / abs(right);
if ((left < 0) != (right < 0)) return -quotient;
return quotient;
}
// 执行一次二元运算,先弹出的是右操作数
ll calc(char op, ll left, ll right) {
if (op == '+') return left + right;
if (op == '-') return left - right;
if (op == '*') return left * right;
if (op == '/') return trunc_div(left, right);
ll result = 1;
for (ll i = 0; i < right; i++) result *= left; // 幂次很小,直接乘
return result;
}
// 弹出栈顶算子并归约
void pop_op() {
char op = ops.back();
ops.pop_back();
ll right = nums.back(); nums.pop_back();
ll left = nums.back(); nums.pop_back();
nums.push_back(calc(op, left, right));
}
int main() {
scanf("%s", expr);
ll len = strlen(expr);
ll num = 0;
bool has_num = false;
for (ll i = 0; i < len; i++) {
char ch = expr[i];
if (ch >= '0' && ch <= '9') {
num = num * 10 + (ch - '0');
has_num = true;
continue;
}
if (has_num) {
nums.push_back(num);
has_num = false;
num = 0;
}
if (ch == '(') {
ops.push_back(ch);
} else if (ch == ')') {
while (ops.back() != '(') pop_op(); // 弹到左括号
ops.pop_back(); // 丢掉左括号
} else { // + - * / ^
while (!ops.empty() && ops.back() != '(' &&
precedence(ops.back()) >= precedence(ch)) {
pop_op(); // 先算优先级更高或同级的
}
ops.push_back(ch);
}
}
if (has_num) nums.push_back(num);
while (!ops.empty()) pop_op();
printf("%lld\n", nums.back());
return 0;
}