进入十一月,青少儿编程赛事进入密集备考期,几大标志性比赛的时间节点都摆在这里:
- 第十八届蓝桥杯软件赛 (C/C++ 程序设计、Python 程序设计均为核心科目)报名已于 2026-10-08 10:00 开启,至 2026-12-18 14:00 截止;省赛 / 全国选拔赛定于 2027 年 4 月(4 月 10、11 日) ,全国总决赛 2027 年 6 月中上旬。
- 中国电子学会 2026 年 12 月青少年软件编程等级考试 (C/C++ 一级至十级、Python 一级至六级)报名 2026-09-23 --- 12-02 16:00 ,全国测试 12-10 19:00--21:00 ,统考 12-12(C/C++ 8:30--10:30 / Python 14:00--15:00)。
- 全国青少年信息素养大赛 2026---2027 赛季算法应用主题赛,大纲明确包含表达式处理、栈与递归等基础算法。
三个赛事共同高频出现一个"看似简单、实际很能拉开差距"的知识点:中缀表达式求值。蓝桥杯填空题常考带括号的四则运算,电子学会 C/C++ 考级把"栈与表达式求值"列为独立能力点,信息素养大赛的算法题也常借表达式解析建模。今天我们用一道原创题,把表达式求值从"人脑心算"打通到"栈 + 调度场算法"的完整能力链。
二、题目 / 项目目标
我们把问题包装成一道校园场景题------「校园科技节积分计算器」:
给定一行算术表达式,只包含非负整数 、运算符
+ - * /以及括号()。运算符优先级为
* /高于+ -,同级从左到右计算,括号改变优先级。约定
/为整数除法、向零取整 (与 C/C++ 一致,例如8 / -4 = -2、10 / 3 = 3)。请计算表达式的值。
样例(计算结果已实机验证):
| 输入表达式 | 计算过程 | 输出 |
|---|---|---|
3 + 4 * 2 / ( 1 - 5 ) |
3 + 8 / (-4) = 3 + (-2) |
1 |
2 * (3 + 4) - 6 / 3 |
2*7 - 2 = 14 - 2 |
12 |
100 + 200 * 3 - 4 / 2 |
100 + 600 - 2 |
698 |
10 / 3 * 2 |
(10/3)*2 = 3*2 |
6 |
三、核心考点
- 栈(Stack) 数据结构:后进先出,是本题的核心容器。
- 运算符优先级 与结合性(同级从左往右)。
- 中缀转后缀:调度场算法(Shunting-yard,E. W. Dijkstra 提出),把人读的中缀表达式变成计算机好算的后缀(逆波兰)表达式。
- 后缀表达式求值:遇到数字压栈,遇到运算符弹两个算一个再压回。
- 多位数与空格处理 :输入常带空格,
12必须当一个数而不是1和2。
四、解法 / 拆解
最清晰、最不容易出错的写法是两步法:先把中缀表达式转换成后缀表达式,再对后缀表达式求值。
4.1 Python 双版实现
python
def evaluate(expr):
# 1) 去掉空格,做基础分词(多位数归并成一个 token)
s = expr.replace(' ', '')
tokens, i, n = [], 0, len(s)
while i < n:
if s[i].isdigit():
j = i
while j < n and s[j].isdigit():
j += 1
tokens.append(s[i:j])
i = j
else:
tokens.append(s[i])
i += 1
# 2) 调度场算法:中缀 -> 后缀
prec = {'+': 1, '-': 1, '*': 2, '/': 2}
out, ops = [], []
for t in tokens:
if t.isdigit():
out.append(t)
elif t == '(':
ops.append(t)
elif t == ')':
while ops and ops[-1] != '(':
out.append(ops.pop())
ops.pop() # 弹出左括号 (
else: # 运算符:栈顶优先级 >= 当前优先级就先弹出
while ops and ops[-1] != '(' and prec[ops[-1]] >= prec[t]:
out.append(ops.pop())
ops.append(t)
while ops:
out.append(ops.pop())
# 3) 后缀求值
st = []
for t in out:
if t.isdigit():
st.append(int(t))
else:
b, a = st.pop(), st.pop()
if t == '+':
st.append(a + b)
elif t == '-':
st.append(a - b)
elif t == '*':
st.append(a * b)
else: # 整数除法向零取整(注意与 Python 的 // 不同)
q = abs(a) // abs(b)
if (a < 0) != (b < 0):
q = -q
st.append(q)
return st[0]
print(evaluate("3 + 4 * 2 / ( 1 - 5 )")) # 1
print(evaluate("2 * (3 + 4) - 6 / 3")) # 12
print(evaluate("100 + 200 * 3 - 4 / 2")) # 698
print(evaluate("10 / 3 * 2")) # 6
4.2 C++ 双版实现
cpp
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <cctype>
using namespace std;
long long eval(const string& expr) {
string s;
for (char c : expr) if (!isspace((unsigned char)c)) s += c;
// 1) 分词
vector<string> tokens;
int i = 0, n = s.size();
while (i < n) {
if (isdigit((unsigned char)s[i])) {
int j = i;
while (j < n && isdigit((unsigned char)s[j])) j++;
tokens.push_back(s.substr(i, j - i));
i = j;
} else {
tokens.push_back(string(1, s[i]));
i++;
}
}
// 2) 调度场:中缀 -> 后缀
auto prec = [](char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0;
};
vector<string> out;
vector<char> ops;
for (auto& t : tokens) {
if (isdigit((unsigned char)t[0])) {
out.push_back(t);
} else if (t == "(") {
ops.push_back('(');
} else if (t == ")") {
while (!ops.empty() && ops.back() != '(')
{ out.push_back(string(1, ops.back())); ops.pop_back(); }
ops.pop_back(); // 弹出 (
} else {
char op = t[0];
while (!ops.empty() && ops.back() != '(' && prec(ops.back()) >= prec(op))
{ out.push_back(string(1, ops.back())); ops.pop_back(); }
ops.push_back(op);
}
}
while (!ops.empty()) { out.push_back(string(1, ops.back())); ops.pop_back(); }
// 3) 后缀求值
stack<long long> st;
for (auto& t : out) {
if (isdigit((unsigned char)t[0])) {
st.push(stoll(t));
} else {
long long b = st.top(); st.pop();
long long a = st.top(); st.pop();
char op = t[0];
if (op == '+') st.push(a + b);
else if (op == '-') st.push(a - b);
else if (op == '*') st.push(a * b);
else st.push(a / b); // C/C++ 整数除法天然向零取整
}
}
return st.top();
}
int main() {
cout << eval("3 + 4 * 2 / ( 1 - 5 )") << "\n"; // 1
cout << eval("2 * (3 + 4) - 6 / 3") << "\n"; // 12
cout << eval("100 + 200 * 3 - 4 / 2") << "\n"; // 698
cout << eval("10 / 3 * 2") << "\n"; // 6
return 0;
}
4.3 复杂度
- 时间复杂度 :每个 token 进出栈各一次,整体 O(n),n 为表达式长度。
- 空间复杂度 :栈与输出序列最多存 O(n) 个元素,O(n)。
五、七易错点
- 多位数 vs 单字符 :必须把连续数字读成一个 token,否则
12会被当成1和2两个数字,结果直接错。 - 除法语义 :C/C++ 的
/是向零取整(truncation) ,不是向下取整(floor);而 Python 的//是 floor,负数时结果不同(-7 // 2 = -4但 C++ 里-7 / 2 = -3),需自己实现向零取整。 - 优先级弹出条件 :必须用
>=才弹出(同优先级从左往右),若写成>会变成右结合,同级运算顺序颠倒。 - 括号不匹配 :缺右括号会让栈里残留
(,应先在输入层做配对校验,否则求值阶段会pop空栈。 - 减号与负号歧义 :若题目允许负数(如
-3 + 2),需要区分"一元负号"与"二元减号",这是进阶难点(见下)。 - 空格与空白字符:真实输入常带空格,分词前务必统一剔除或跳过,否则把空格当 token 会直接崩溃。
- 整数溢出 :连乘可能超过
int范围,用long long;极端情况如-2147483648 / -1在 32 位整数下会溢出,需警惕。
六、进阶
- 双栈直接求值:不转后缀,用"数字栈 + 运算符栈",遇到优先级更高的运算符或右括号时再就地计算,代码更短但括号处理更绕。
- 支持幂运算
^:优先级最高且右结合 (2^3^2 = 2^(3^2) = 512),调度场里要用>而非>=判断同优先级弹出。 - 支持一元负号 / 正号 :当
-出现在表达式开头、或紧跟在(,+,-,*,/之后时,它就是一元负号,可先塞一个0变成0 - x来复用二元减法逻辑。 - 支持变量与函数 :如
max(a, b)、abs(x)、sin(x),本质是扩展分词器与运算符表,这正是真实计算器 / 编译器前端的雏形。 - 与编译原理的关系:表达式求值 = "词法分析(分词)+ 语法分析(优先级)+ 语义计算(栈)"的迷你版,是理解编译器的第一步。
- 真题链接:蓝桥杯与电子学会考级常考"带括号的四则运算求值"或"中缀转后缀填空",把本篇的调度场算法背熟,这类题基本白给。
七、小结与互动
中缀表达式求值看似是入门题,却是栈 这一数据结构的经典应用,也是蓝桥杯、电子学会 C/C++ 与 Python 考级、信息素养大赛算法赛项共同的必考点。核心记住三句话:分词把多位数归并、调度场用 >= 弹同优先级、求值弹二算一再压回 ;再补一个坑------整数除法向零取整,别踩 Python // 的 floor 陷阱。
你平时刷题遇到过"负号和减号分不清"或者"除法取整方向写反"的坑吗?欢迎在评论区聊聊你见过的表达式求值变形题,我们一起把这类基础题练到"闭眼秒杀"。
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:
- 全国青少年信息素养大赛复赛集训题目Python&C++.docx
https://pan.quark.cn/s/93995d3cb150 - 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
https://pan.quark.cn/s/da97b5dbf75d - Python背记手册.pdf
https://pan.quark.cn/s/7568ae9ca92b - Python课程
https://pan.quark.cn/s/a94bf02d00c6 - 2024信息素养大赛图形化复赛集训题答案3-9
https://pan.quark.cn/s/6ccab7ec3cbc - 2025年03月份电子学会考级真题
https://pan.quark.cn/s/4403c4228912 - 2025全国青少年信息素养大赛赛项说明
https://pan.quark.cn/s/d9d0df4a9f29 - 青少儿信息素养大赛编程资料
https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注获取最新分享。