蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:中缀表达式求值解析

进入十一月,青少儿编程赛事进入密集备考期,几大标志性比赛的时间节点都摆在这里:

  • 第十八届蓝桥杯软件赛 (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

三、核心考点

  1. 栈(Stack) 数据结构:后进先出,是本题的核心容器。
  2. 运算符优先级 与结合性(同级从左往右)。
  3. 中缀转后缀:调度场算法(Shunting-yard,E. W. Dijkstra 提出),把人读的中缀表达式变成计算机好算的后缀(逆波兰)表达式。
  4. 后缀表达式求值:遇到数字压栈,遇到运算符弹两个算一个再压回。
  5. 多位数与空格处理 :输入常带空格,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)。

五、七易错点

  1. 多位数 vs 单字符 :必须把连续数字读成一个 token,否则 12 会被当成 1 和 2 两个数字,结果直接错。
  2. 除法语义 :C/C++ 的 / 是向零取整(truncation) ,不是向下取整(floor);而 Python 的 // 是 floor,负数时结果不同(-7 // 2 = -4 但 C++ 里 -7 / 2 = -3),需自己实现向零取整。
  3. 优先级弹出条件 :必须用 >= 才弹出(同优先级从左往右),若写成 > 会变成右结合,同级运算顺序颠倒。
  4. 括号不匹配 :缺右括号会让栈里残留 (,应先在输入层做配对校验,否则求值阶段会 pop 空栈。
  5. 减号与负号歧义 :若题目允许负数(如 -3 + 2),需要区分"一元负号"与"二元减号",这是进阶难点(见下)。
  6. 空格与空白字符:真实输入常带空格,分词前务必统一剔除或跳过,否则把空格当 token 会直接崩溃。
  7. 整数溢出 :连乘可能超过 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 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

相关推荐
灿飞屋2 小时前
Kotlin与Java虚拟机四:函数
java·python·kotlin·安卓·android jetpack
武子康2 小时前
Qwen-Drive 到底是什么:4B 级 VLM 如何成为驾驶策略底座
人工智能·算法·机器学习
坤坤子吖3 小时前
Linux网络编程:TCP服务器的多进程与多线程实现
linux·服务器·开发语言·网络·c++·笔记·学习
一晌小贪欢3 小时前
Python基础:第 09 篇 · while 循环:重复直到不满足
开发语言·c++·python·数据可视化·面向对象·python办公
zh_xuan4 小时前
LeeCode 496. 下一个更大元素I
数据结构·算法·栈·单调栈
机 _ 长4 小时前
05 智算运维|8 张卡怎么才算「捆成一张」:NCCL 实测与拓扑
运维·人工智能·算法·智算运维
xiaoye-duck4 小时前
《C++ 项目:手写大模型接入 SDK》LLMManager架构设计与实现
开发语言·网络·c++·语言模型
一条大祥脚4 小时前
CuTe 与 CuTe DSL:从 C++ 模板元编程到 Python DSL 的 GPU 编程演进
开发语言·c++·python·cutlass·cute·cute dsl
·present·5 小时前
CSAPP(lecture 13_02 Linking)
c语言·c++