蓝桥杯·全国青少年信息素养大赛·电子学会C/C++考级:拓扑排序课程表排课解析

第十八届蓝桥杯大赛正处于报名季 (软件赛、电子赛报名 2026-10-08---12-18,省赛 2027-04 开赛),全国青少年信息素养大赛算法应用赛项、电子学会 C/C++ 考级(12 月场次报名 9/23---12/2)也都在火热备赛中。这几类赛事的算法题里,有一类「前后顺序约束」问题反复出现:某门课必须先于另一门课学、某个工序必须等前置工序完成、某份文件依赖另一些文件先生成......这类题的本质,就是一个经典图论算法------拓扑排序(Topological Sort)。

本文用一道原创题「校园课程表排课」串起拓扑排序的建模、Kahn 算法、判环、复杂度与进阶,附 C++/Python 双版代码,建议收藏练手。

一、题目目标(原创题)

校园课程表排课

学校开设了 n 门课程,编号 1 ~ n。部分课程有先修要求:若 a 必须在 b 之前学完,记为一条依赖 a → b。

请你判断:能否安排出一种上课顺序,使所有先修要求都满足(即不存在「绕回自己」的循环依赖)?若能,输出任意一种可行的上课顺序。

这正是蓝桥杯 C/C++ 程序设计、信息素养算法赛项里「依赖 / 工序 / 排队」类题目的标准模型,也是电子学会 C/C++ 考级六级图论基础的必会考点。

  • 能排课 ⇔ 依赖关系构成有向无环图(DAG);
  • 排不课 ⇔ 依赖关系里存在环(比如 A 依赖 B、B 依赖 C、C 又依赖 A,谁都没法学)。

二、核心考点

  1. 图建模 :把课程当点、先修依赖当有向边 a → b,存入邻接表。
  2. 入度(indegree):指向某点的边数。入度为 0 的课「没有任何前置」,可以立刻开课。
  3. Kahn 算法:用队列不断把「当前入度为 0 的点」取出来排课,并把它的后继入度减一;后继入度降到 0 时再入队。
  4. 环检测 :若最终排出来的课数 < n,说明还有课永远等不到入度归零------必存在环。
  5. 可行顺序输出:队列弹出的顺序本身就是一种拓扑序。

三、解法拆解(Kahn 算法)

一句话思路:谁没有前置,谁先上;上完一门,就把它「解锁」的后继往前推一格。

步骤:

  1. 统计每个点的入度 indeg[v];

  2. 把所有 indeg == 0 的点入队;

  3. 循环:弹出队首 u 加入答案,遍历 u 的每个后继 v,令 indeg[v]--,若降到 0 则 v 入队;

  4. 结束时若答案长度等于 n,输出答案;否则报告「存在循环依赖」。

C++ 实现(Kahn)

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;                   // n 门课,m 条先修依赖
    vector<vector<int>> g(n + 1);    // 邻接表,课程编号 1..n
    vector<int> indeg(n + 1, 0);
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;               // a 必须在 b 之前:a -> b
        g[a].push_back(b);
        indeg[b]++;
    }
    queue<int> q;
    for (int i = 1; i <= n; i++)
        if (indeg[i] == 0) q.push(i);
    vector<int> order;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        order.push_back(u);          // 排上这门课
        for (int v : g[u]) {
            if (--indeg[v] == 0) q.push(v);
        }
    }
    if ((int)order.size() != n) {
        cout << "无法排课:存在循环先修依赖\n";
    } else {
        for (int x : order) cout << x << " ";
        cout << "\n";
    }
    return 0;
}

Python 实现(Kahn)

python 复制代码
from collections import deque

def topo_sort(n, edges):
    g = [[] for _ in range(n + 1)]
    indeg = [0] * (n + 1)
    for a, b in edges:           # a 必须在 b 之前:a -> b
        g[a].append(b)
        indeg[b] += 1
    q = deque(i for i in range(1, n + 1) if indeg[i] == 0)
    order = []
    while q:
        u = q.popleft()
        order.append(u)
        for v in g[u]:
            indeg[v] -= 1
            if indeg[v] == 0:
                q.append(v)
    return order if len(order) == n else None   # None 表示有环

n, m = map(int, input().split())
edges = [tuple(map(int, input().split())) for _ in range(m)]
res = topo_sort(n, edges)
print("无法排课:存在循环先修依赖" if res is None else " ".join(map(str, res)))

样例 :n = 5,依赖 (1,2) (1,3) (2,4) (3,4) (4,5),运行得到可行顺序 1 2 3 4 5。

有环样例 :(1,2) (2,3) (3,1),程序输出「无法排课」,因为谁都不满足入度为 0。

四、对比:DFS 三色标记判环

除了 Kahn,还可以用深度优先搜索 + 三色标记判环:白=未访问、灰=正在访问(在递归栈中)、黑=已访问完。若在 DFS 中遇到「灰色」邻居,说明从它又走回了自己------即存在环。

python 复制代码
def has_cycle(n, edges):
    g = [[] for _ in range(n + 1)]
    for a, b in edges:
        g[a].append(b)
    WHITE, GRAY, BLACK = 0, 1, 2
    color = [WHITE] * (n + 1)
    def dfs(u):
        color[u] = GRAY
        for v in g[u]:
            if color[v] == GRAY:        # 回到正在访问的祖先 => 环
                return True
            if color[v] == WHITE and dfs(v):
                return True
        color[u] = BLACK
        return False
    return any(color[i] == WHITE and dfs(i) for i in range(1, n + 1))
  • Kahn:额外得到一种拓扑序,代码直观,判环顺带完成;
  • DFS 三色:不依赖队列、不改图结构,判定「有没有环」更紧凑,但想要具体顺序要再跑一遍。

五、时间与空间复杂度

  • 时间 :每个点入队/出队一次,每条边被扫描一次 → O(V + E)(V 为课程数,E 为依赖数)。
  • 空间 :邻接表、入度数组、队列各 O(V + E) → O(V + E)。

对比「盲目枚举全排列再校验」(最坏 O(V!·E)),拓扑排序把指数级暴力直接压成线性,这正是它被赛事青睐的原因。

六、易错点(6 个,考试最容易丢分)

  1. 入度统计漏边 / 重边 :a → b 和 a → b 是两条边时,indeg[b] 要 +2;用 map 去重或按原始边计数,二者不要混。
  2. 队列空后不校验点数 :只写「输出队列内容」却忘了判断 order.size() == n,有环时也会输出部分顺序,判题直接判错。
  3. 自环 :a → a 这种依赖,入度永远不为 0,必须靠「点数不等于 n」兜住,不能假设输入无自环。
  4. 多个连通块 :图可能分成几块互不相连,初始化入度 0 的点要全部入队,而不是只入队第一个。
  5. 编号偏移 :题目常给 1-indexed,数组却开成 n 大小(应为 n+1),导致最后一门课越界。
  6. 顺序 ≠ 唯一:拓扑序本来就有很多种,判题通常只验「是否合法」,别纠结和样例顺序「长得不一样」。

七、进阶

① 字典序最小的拓扑序

把队列换成小根堆(优先队列),每次弹当前编号最小的入度为 0 的点即可。注意堆里只放「刚降到 0」的点,不是每次全图扫一遍。

python 复制代码
import heapq
def topo_min_lex(n, edges):
    g = [[] for _ in range(n + 1)]
    indeg = [0] * (n + 1)
    for a, b in edges:
        g[a].append(b); indeg[b] += 1
    heap = [i for i in range(1, n + 1) if indeg[i] == 0]
    heapq.heapify(heap)
    order = []
    while heap:
        u = heapq.heappop(heap)
        order.append(u)
        for v in g[u]:
            indeg[v] -= 1
            if indeg[v] == 0:
                heapq.heappush(heap, v)
    return order if len(order) == n else None

样例 (1,2)(1,3)(2,4)(3,4)(4,5) 会得到最小字典序 1 2 3 4 5。

② 拓扑排序 + DP:求「最长课程链」

若问「最少需要几个学期,每学期可并行上多门无依赖的课」,可令 dist[v] = max(dist[u]+1),在 Kahn 松弛时顺手更新,最终 max(dist) 就是答案。样例最长链为 4(如 1→2→4→5)。

③ 唯一拓扑序判定 :任意时刻队列/堆里恰好只有 1 个入度为 0 的点,拓扑序才唯一;否则有多种。

④ 输出所有拓扑序:回溯枚举每次可选的入度 0 集合,常用于「全排列型」搜索题。

八、小结与互动

拓扑排序是图论里性价比极高的一招:Kahn 算法 O(V+E) 线性搞定「依赖排序 / 工序安排 / 判环」,也是后续关键路径、拓扑 DP、编译依赖、任务调度的基石。蓝桥杯的「排队打水变形」、信息素养的「流程依赖」、电子学会 C/C++ 考级的图论章节,都绕不开它。

👉 你练手时遇到过「拓扑序不唯一」「字典序最小」这类变体吗?或者想看下一期拆解哪个算法(并查集进阶 / 最短路径 / 动态规划)?欢迎在评论区留言,下一篇就写它!

如果觉得有用,点赞 + 收藏 + 关注,带你用一篇一个算法的方式啃完少儿编程赛事高频考点。


📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享,点击链接可直接保存;若需在 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

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

相关推荐
不会算法的笨小孩1 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
LabVIEW开发1 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
天天被压力2 小时前
【A股分钟级高频数据完全手册 #07】北交所历史分钟K线接口:京市Pro版分时批量
java·人工智能·python
天天被压力2 小时前
【A股分钟级高频数据完全手册 #08】股票日、分时数据接口对比:普通分时与Pro版差异实测
java·人工智能·python
特立独行的猫A2 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python
小lo想吃棒棒糖3 小时前
把机器人训练从 GPU 搬到昇腾 NPU:启元 Q1 起身动作 PPO 训练全记录
python·机器人·昇腾·启元机器人
Thanks_ks3 小时前
【第 006 讲】搭建 PyCharm 开发环境:PyCharm 的下载与安装 | 初始设置与常用配置
ide·python·pycharm·开发工具·jetbrains·开发环境·集成开发环境
严肃小白兔3 小时前
MATLAB 数值计算中的精度与误差问题:浮点运算、误差传播与数值稳定性
人工智能·算法·matlab
Hhy_11073 小时前
《C++深度解构06》手撕 string —— 从底层内存管理到深浅拷贝彻底吃透
c语言·开发语言·c++·学习·visual studio
青少儿编程课堂4 小时前
蓝桥杯青少组图形化省考报名倒计时·全国青少年信息素养大赛·电子学会图形化考级:校园黑白棋人机对战解析
c++·python·算法·bfs·信息学竞赛