想象你来到一座陌生城市,手里只有一张地铁线路图。站点是圆的,轨道是线,换乘通道把不同线连在一起。你要判断从站 A 到站 B 怎么走最快,要设计一套“最少换乘”方案,要找出如果某条线路断了会不会有站点失联——这些问题背后都是一门叫“图论”的学科。对 AI 求职者来说,图论不是数学游戏,而是面试中最高频的算法题母题之一:推荐系统里的好友关系、自动驾驶里的路径规划、大模型训练任务的有向依赖,全都能抽象成一张图。这篇文章会用“城市地铁图”这个比喻贯穿全程,带你从零看懂图论算法面试的核心套路:怎么存图、怎么遍历、怎么求最短路径、怎么保证工程最省,以及面试时怎么做题不踩坑。

岗位关联

图论是算法面试的“地基性考点”,掌握它不仅能应对 hard 题,更能让你在面对社交网络、知识图谱、任务调度等 AI 场景时,快速把业务问题翻译成可计算的模型。

目录
  1. 可视化演示
  2. 基础入门
  3. 存储选型
  4. DFS 遍历
  5. BFS 遍历
  6. 最短路径
  7. 最小生成树
  8. 拓扑排序
  9. 面试实战
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示图论算法面试精讲的核心机制,通过输入/处理/存储/输出四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

图论算法面试精讲:图的表示 图的表示与遍历基础 最短路径算法精讲 核心机制可视化 图论算法面试精讲整体架构与关键组件

基础入门

如果你今天拿到一张陌生城市的地铁图,你会先看什么?多数人会先看两个东西:站点数量和轨道数量。站点太少的地图很空,轨道太多的地方容易绕晕。图论的第一课就是这个感觉:图由顶点(站点)和边(轨道)组成,我们关心顶点个数 \(V\) 和边数 \(E\),因为后续所有算法的时间复杂度都围绕它们展开。

从地铁图过渡到数学表达:顶点记作 \(V\),边记作 \(E\)。边有方向的是有向图,没有方向的是无向图。地铁线路通常是双向的,对应无向图;而“公众号关注关系”“网页链接”是单向的,对应有向图。面试题里还会出现“带权图”,可以理解成每段轨道有不同行驶时长;权值相同或未指定的图就是无权图。

图论里有一条最著名的“握手引理”:所有站点度数之和等于边数的两倍。度就是这个站点连接的轨道数。为什么?因为每一条边都连接两个端点,在计算总度数时恰好被数了两次。

回到地铁比喻:你不需要背公式,只要记住“每段轨道有两个头”。面试时如果题目给了站点和轨道数,优先检查数据规模,再决定用哪种算法。读图如读地图,先看规模,再做选择。

∑v∈Vdeg⁡(v)=2∣E∣\displaystyle \sum_{v \in V} \deg(v) = 2|E|
这条公式常用来快速验证输入数据是否自洽,例如奇数度的顶点个数一定是偶数。
  1. 累加度数把每一个站点的连接轨道数量加起来。
    ∑v∈Vdeg⁡(v)\displaystyle \sum_{v \in V} \deg(v)
  2. 每条边贡献两次一条轨道有两个端点,分别给两个站点各增加 1 度。
    ∑v∈Vdeg⁡(v)=2∣E∣\displaystyle \sum_{v \in V} \deg(v) = 2|E|
  3. 奇偶推论左边是偶数,说明奇数度站点的总数也必须为偶数。
    ∑deg⁡(v)=2∣E∣⇒奇数度顶点数为偶数\displaystyle \sum \deg(v) = 2|E| \Rightarrow \text{奇数度顶点数为偶数}

存储选型

一座城市的地铁图有很多种保存方式。最笨的方法是做一块巨大的“全城换乘查询屏”,把每个站点到每个站点是否有轨道都标出来,这叫邻接矩阵。另一种做法是每个站点门口挂一块“本站可直达”的小牌子,这叫邻接表。还有一种是把轨道工程档案一条条列成数组,这叫边集数组。

三种方式各有适用场景。邻接矩阵用二维数组 \(graph[u][v]\) 表示站点 \(u\) 到 \(v\) 是否有边,判断两点是否相邻只需 \(O(1)\),但空间固定是 \(V^2\)。邻接表为每个顶点保存一个邻居列表,只记录真实存在的边,空间约等于 \(V + 2E\)。边集数组则把所有边按 (起点, 终点, 权值) 存放,特别适合需要按权值排序的算法,比如 Kruskal 最小生成树。

用比喻收尾:查询屏适合站点少、轨道密的“中心城区”;邻接表像每个站台挂一张小卡片,适合铺得很开的地铁网络;边集数组则是施工队的图纸清单。面试时先问自己:“这个图边多不多?” 一句话就能锁定存储方案。

邻接矩阵:V2vs邻接表:V+2E\text{邻接矩阵}:V^2 \quad \text{vs} \quad \text{邻接表}:V + 2E
空间选择的关键是判断图是“稠密”还是“稀疏”:边数接近 \(V^2\) 选矩阵,边数远小于 \(V^2\) 选邻接表。
  1. 矩阵空间邻接矩阵必须开一个 \(V \times V\) 的表格。
    V2V^2
  2. 表空间邻接表只保存每个顶点和它真实的邻居。
    V+2E=∑v(1+deg⁡(v))V + 2E = \sum_{v} (1 + \deg(v))
  3. 对比结果当 \(E \ll V^2\) 时,邻接表能省下大量空间。
    V+2E≪V2V + 2E \ll V^2

DFS 遍历

想象一位地铁检修员从 1 号线起点出发,沿着一条支线一直往前走,走到尽头后原路退回最近的岔路口,再换另一条支线继续走。这种“一条路走到黑,再回溯”的巡查方式,就是深度优先搜索(DFS)。

DFS 的关键是递归加上回溯。递归函数进入一个顶点后,标记它“已访问”,然后依次访问每一个未访问的邻居。等到所有邻居都处理完,函数自然返回上一层。这个过程可以抽象成下面这个模板:先做决定,再进邻居,最后撤销现场。

回到地铁检修员:他不需要地图,只需要记住“刚才从哪条路来的”,走完所有岔路后整张网的连通情况就清楚了。DFS 在面试中常用于判断连通分量、检测环、求路径存在性,但注意它不擅长找“最短路径”,因为深挖的路线通常不是最短的。

T(V,E)=O(V+E)T(V,E) = O(V + E)
DFS 和 BFS 遍历整张图的时间复杂度都是 \(O(V+E)\),因为每个顶点和每条边最多被访问一次。
  1. 访问顶点每进入一个站点,就把它标成已访问。
    visit(v)\text{visit}(v)
  2. 递归邻居对每个未访问邻居 \(u\) 继续调用 DFS。
    DFS(u),∀u∈neighbors(v)\text{DFS}(u),\quad \forall u \in \text{neighbors}(v)
  3. 累计代价每个顶点进入一次,每条边被查看一次。
    O(V)+O(E)=O(V+E)O(V) + O(E) = O(V + E)

BFS 遍历

如果地铁失火,广播系统会怎么做?它不会沿着一条线路追到底,而是以事发站为圆心,一圈一圈向外通知:先通知直达站,再通知换乘站,再通知更远的站。这种“逐层扩散”的搜索方式,就是广度优先搜索(BFS)。

BFS 的实现核心是队列,先进先出。起点入队;每次从队首弹出一个站点,把它的所有未访问邻居入队;因为队列按进入顺序处理,所以先被发现的站点一定比后发现的站点离起点更近。因此在无权图中,BFS 天然能求出起点到每个点的最短路径长度。

用广播的比喻来理解:BFS 永远先通知“近的人”,再通知“远的人”,所以首次收到通知的路径一定是最少经过站点的路径。面试中,“最短换乘次数”“社交网络几度人脉”“迷宫最少步数”这类题目,都是在考 BFS。

d[v]=d[u]+1d[v] = d[u] + 1
这行式子表示:当从 \(u\) 第一次发现 \(v\) 时,\(v\) 到起点的距离就是 \(u\) 到起点的距离加 1。BFS 的“第一次发现”等价于“最短距离”。
  1. 起点距离起点到自己的距离是 0。
    d[start]=0d[\text{start}] = 0
  2. 邻居发现从队列取出 \(u\),检查它的邻居 \(v\)。
    v∈neighbors(u)v \in \text{neighbors}(u)
  3. 距离累加如果 \(v\) 第一次被访问,则更新距离。
    d[v]=d[u]+1d[v] = d[u] + 1

最短路径

坐地铁导航时,你希望 App 告诉你“从当前站到目的地,哪条路线总耗时最少”。如果每条轨道的行驶时间是已知的,这就是带权最短路径问题。最常见的算法有三种:Dijkstra、Bellman-Ford 和 Floyd。

Dijkstra 像一个精明的导航员:它维护一个“当前已知最短用时表”,每次都从尚未确定的站点里挑一个当前距离最小的站点出来,并用它去更新邻居。这个贪心策略要求所有边权非负,否则先选出来的“最小”可能是错的。核心更新操作叫“松弛”:

回到地铁场景:Dijkstra 适合“所有轨道都不会倒贴时间”的正常城市。如果存在负权边——相当于某条线路不仅不耗时还能“倒赚时间”,Dijkstra 就会被绕晕,这时要改用 Bellman-Ford,它会把所有边反复松弛 \(V-1\) 轮。如果面试要求所有站点两两之间的最短路径,则用 Floyd 动态规划,一次算出整个二维距离表。

d[v]=min⁡(d[v],d[u]+w(u,v))d[v] = \min(d[v], d[u] + w(u,v))
松弛就是问一句话:先到 \(u\) 再到 \(v\),会不会比原来已知的到 \(v\) 路线更快?如果更快,就更新。
  1. 当前距离\(d[v]\) 是目前已知从起点到 \(v\) 的最短用时。
    dist[v]\text{dist}[v]
  2. 候选路线经过 \(u\) 再到 \(v\) 的总用时。
    d[u]+w(u,v)d[u] + w(u, v)
  3. 取最小值如果候选更短,就更新当前纪录。
    d[v]←min⁡(d[v],d[u]+w(u,v))d[v] \leftarrow \min(d[v], d[u] + w(u,v))

最小生成树

假设地铁公司要在多个新区之间铺设轨道,要求所有站点连通,且轨道总造价最低。注意,我们不是求“从 A 到 B 怎么走最快”,而是求“把所有站点连成一个整体最少花多少钱”——这就是最小生成树(MST)问题。

Prim 算法像是从一个已连通的“城市组团”出发,每次都从组团外挑一条通向组团的最短边扩进来。Kruskal 算法则把所有边按造价从小到大排序,从便宜到贵一条条挑选,只要不形成环就加入。两者背后共用同一条“切分性质”:把站点分成两组,横跨两组的边里造价最小的那条,一定属于某棵最小生成树。

用修地铁比喻:Prim 是从已开发的城区向外“长”出去,Kruskal 是先把所有候选轨道标好价格,从便宜到贵慢慢合并。面试里只要听到“连通所有点且总代价最小”,就立刻把题目翻译成 MST。

w(e∗)=min⁡e∈δ(S)w(e)w(e^*) = \min_{e \in \delta(S)} w(e)
切分性质说:任意把一个图切成两部分,连接这两部分的最短那条“桥梁边”,一定值得保留在最小生成树里。
  1. 切分集合把顶点分成两个集合 \(S\) 和 \(V \setminus S\)。
    δ(S)={(u,v)∣u∈S,v∉S}\delta(S) = \{(u,v) \mid u \in S, v \notin S\}
  2. 最小跨边在所有跨两个集合的边里找到权值最小的那条。
    min⁡e∈δ(S)w(e)\min_{e \in \delta(S)} w(e)
  3. 必然出现这条最小的跨边一定存在于某个最小生成树中。
    w(e∗)=min⁡e∈δ(S)w(e)w(e^*) = \min_{e \in \delta(S)} w(e)

拓扑排序

如果你负责规划一整条地铁线路的施工:必须先挖隧道,再铺轨道,再装信号系统,最后试运行。这些任务之间有明显的前后依赖,不能乱序。把每个任务看作顶点,任务依赖看作有向边,就得到一个有向无环图(DAG)。

拓扑排序就是给 DAG 里的所有任务排出一个合法执行顺序:每条有向边都必须从“先做的事”指向“后做的事”。经典算法是 Kahn 算法:统计每个顶点的入度,把入度为 0 的顶点先输出,并删除它的所有出边,重复这个过程直到所有顶点输出完毕。如果最后还有顶点未被输出,说明图中存在环,任务计划不可执行。

回到施工比喻:拓扑序不是唯一的。比如铺轨道和装信号系统之间如果没有直接依赖,谁先谁后都可以。面试中如果题目提到“依赖关系”“编译顺序”“课程先修”,第一反应就应该是拓扑排序。

(u,v)∈E⇒order(u)<order(v)(u, v) \in E \Rightarrow \text{order}(u) < \text{order}(v)
拓扑序的核心约束只有一句话:有依赖关系的两个任务,前一个必须排在后面那个前面。
  1. 前驱约束如果边 \(u \rightarrow v\) 存在,则 \(u\) 必须先执行。
    (u,v)∈E(u,v) \in E
  2. 顺序要求在最终序列里 \(u\) 的下标必须小于 \(v\)。
    order(u)<order(v)\text{order}(u) < \text{order}(v)
  3. 入度消减Kahn 算法不断删掉入度为 0 的节点,直到删完。
    in[v]=0, v 可输出\text{in}[v] = 0,\ v \ \text{可输出}

面试实战

面试做题就像拿到一张城市地图但不告诉你该坐几号线。你需要快速完成三件事:读题建模、复杂度估算、代码规范。

读题建模是把业务语言翻译成图论语言。“好友推荐”是找邻居;“最少换乘”是 BFS;“物流运费最低”是带权最短路;“所有仓库联通成本最小”是 MST;“课程依赖”是拓扑排序。然后根据数据规模选择算法:如果 \(V\) 只有几十,Floyd 也可以;如果 \(V\) 是十万,必须用堆优化的 Dijkstra 或邻接表 BFS。

回到地铁比喻:优秀的面试者不是记路名,而是拿到地图先确认“有多少站、多少条轨道”,再决定用哪套出行工具。最后还要写出变量清晰的代码:graph、dist、visited 不要随手写成 g、d、vis。算法正确,再加上可读性,才是真正的面试高分答案。

O(Elog⁡V)≪O(V2)O(E \log V) \ll O(V^2)
工程中很多“看似暴力”的做法会因数据规模爆炸而超时。选算法前先算复杂度,是面试的基本素养。
  1. 朴素方案在稠密图上用邻接矩阵跑最短路。
    O(V2)O(V^2)
  2. 优化方案用优先队列加邻接表后大幅下降。
    O(Elog⁡V)O(E \log V)
  3. 规模判断稀疏图下两种方案差距巨大。
    O(Elog⁡V)≪O(V2)O(E \log V) \ll O(V^2)

AI 应用场景

社交网络“几度人脉”推荐

阶段:特征工程与图挖掘。

解决的问题:传统好友推荐只看共同好友数量,无法表达“关系远近”。用 BFS 可以计算两个用户之间的最短熟人链长度,从而把“二度人脉”作为强推荐信号。

具体案例:招聘平台根据候选人与其潜在人脉的 BFS 距离,优先推荐距离为 2 的行业前辈;配合边权刻画亲密程度后,再用 Dijkstra 求“综合亲密度最高”的推荐路径。

AI训练任务依赖调度

阶段:数据流水线与训练任务编排。

解决的问题:一个模型训练要经过数据清洗、特征提取、模型训练、评估上线等步骤,存在严格依赖。用拓扑排序可以自动生成一条合法执行顺序,并检测依赖环。

具体案例:某 MLOps 平台把每个数据任务建模为 DAG,用 Kahn 算法排布每日训练流程;发现依赖环时自动告警,避免两个任务互相等待形成死锁。

物流路径规划

阶段:决策优化。

解决的问题:配送网络中存在大量站点和路线,不同路线成本不同,需要实时求最低成本路线。Dijkstra 的“松弛”思想可直接用于地图导航和路径规划。

具体案例:无人车配送系统把路口建模为顶点、道路建模为带权边,用优先队列优化 Dijkstra 计算每单的最优路线;若道路出现负向“奖励”,则切换 Bellman-Ford 保证正确性。

Python 演示

python33 行
import heapq def dijkstra(n, edges, start):    graph = [[] for _ in range(n)]    for u, v, w in edges:        graph[u].append((v, w))        graph[v].append((u, w))     dist = [float('inf')] * n    dist[start] = 0    pq = [(0, start)]     while pq:        d, u = heapq.heappop(pq)        if d > dist[u]:            continue        for v, w in graph[u]:            nd = d + w            if nd < dist[v]:                dist[v] = nd                heapq.heappush(pq, (nd, v))     return dist if __name__ == '__main__':    edges = [        (0, 1, 4),        (0, 2, 1),        (2, 1, 2),        (1, 3, 1),        (2, 3, 5),    ]    print(dijkstra(4, edges, 0))
目的:用优先队列优化 Dijkstra,在稀疏带权图上高效求出单源最短距离数组。
  1. graph = [[] for _ in range(n)]建邻接表,每个顶点对应一个邻居列表。
  2. graph[v].append((u, w))无向图要加两条边,有向图只需加一条。
  3. dist[start] = 0起点到自己的最短距离是 0。
  4. heapq.heappop(pq)每次弹出当前距离最小的未确定顶点。
  5. if d > dist[u]: continue这是经典的“懒删除”,跳过已经过期的旧纪录。
  6. for v, w in graph[u]遍历当前站点的所有直达邻居。
  7. dist[v] = nd发现更短的路线就立刻更新。
  8. heapq.heappush(pq, (nd, v))新纪录进入优先队列,等待下一次扩展。

常见误区

误区我觉得 DFS 能走遍所有路径,只要把所有路径都找出来再取最小值,不就能求最短路径了吗?所以最短路径题都用 DFS。

正解DFS 确实能遍历所有路径,但路径数量是指数级增长。无权图求最少经过边数应优先用 BFS,带权非负图用 Dijkstra。只有遇到负权边且不能用 Dijkstra 时,才考虑 Bellman-Ford 或 Floyd。DFS 更适合判断连通性、找环,而不是求最短路。

为何常见:初学者容易把“能搜索”等同于“高效搜索”,忽略了指数量级的路径爆炸。

误区邻接矩阵写起来最直观,判断两个点是否相邻也快,我就一直用邻接矩阵。

正解邻接矩阵空间是 \(V^2\)。当顶点数达到 \(10^5\) 时,矩阵要开 \(10^{10}\) 个位置,直接爆内存。面试中应该先判断稀疏还是稠密:稀疏图用邻接表,稠密图才适合邻接矩阵。

为何常见:因为刷题初期数据规模小,矩阵的坏处没有暴露;遇到大数据用例才突然超时或超内存。

误区Dijkstra 每次都找最小距离,看起来任何边权都能处理,负权只是让距离变小而已。

正解Dijkstra 一旦确定某个点的最短距离,就不会再更新它。负权边可能让“已经确定”的点从另一条路径获得更小距离,算法因此失效。如果图中存在负权边但无负环,应使用 Bellman-Ford。

为何常见:贪心直觉在正权图中成立,换成负权时“当前最小”就不再可靠,这是贪心算法边界的典型误区。

误区拓扑排序把任务排成一个顺序,所以结果应该是唯一的,不然怎么保证执行正确?

正解只要两个任务之间没有直接或间接依赖关系,它们就可以互换顺序。比如同时有两个入度为 0 的任务,Kahn 算法可以任选一个输出。所以 DAG 的拓扑序通常有多个,只有全序关系下才唯一。

为何常见:大脑容易把“算法输出一个结果”理解成“必须只有一个结果”,忽视了 DAG 中本可以并行的任务。

误区Prim 和 Kruskal 都能生成最小生成树,所以这道题的答案应该一样。

正解当图中存在多条权值相同的边时,不同的选边顺序可能得到不同的最小生成树,但总权值一定相同。面试中如果问你“树是否唯一”,要额外检查是否存在权值相等的候选边。

为何常见:忽略“最小总权值”和“具体哪棵树”的区别,容易把求解目标误当成唯一解。

面试题

Q1. 图的基本组成是什么?

单选题 · easy

  • A.顶点和边← 正确答案
  • B.表格和行
  • C.点和点集
  • D.栈和队列

答案:A。图是由顶点和边构成的结构,顶点表示对象,边表示对象之间的关系。表格和行更像数据的存储形态,点和点集不是标准图论概念,栈和队列是线性数据结构,都不符合图的定义。

Q2. 无向图中所有顶点的度数之和等于多少?

单选题 · easy

  • A.顶点数量的两倍
  • B.边数的两倍← 正确答案
  • C.边数本身
  • D.顶点数本身

答案:B。每条无向边连接两个顶点,会在计算总度数时被两个端点各计一次,所以所有顶点度数之和等于边数的两倍。这是握手引理,常用来检查输入数据是否自洽。

Q3. 无权图中求起点到终点的最少经过边数,应优先选择哪种算法?

单选题 · mid

  • A.深度优先遍历(DFS)
  • B.广度优先遍历(BFS)← 正确答案
  • C.随机游走
  • D.朴素邻接矩阵乘法

答案:B。BFS 按层扩散,第一次到达终点时经过的边数一定最少。DFS 走到底再回溯,无法保证路径最短。随机游走没有确定性答案,矩阵乘法时间复杂度太高,面试中不是首选。

Q4. Dijkstra 算法能正确处理哪种图?

单选题 · mid

  • A.边权全部非负的图← 正确答案
  • B.存在负权边的图
  • C.存在负环的图
  • D.任意带权有向图

答案:A。Dijkstra 依赖贪心选择当前最小距离,一旦确认就不再更新。若存在负权边,当前最小可能被后来路径更新,贪心失效。因此它只能用于边权非负的图。

Q5. 图中存在负权边但无负环,求单源最短路应首选哪种算法?

单选题 · mid

  • A.Dijkstra 算法
  • B.Floyd 算法
  • C.Bellman-Ford 算法← 正确答案
  • D.Prim 算法

答案:C。Bellman-Ford 会反复松弛所有边,能正确处理负权边且无负环的情况。Dijkstra 不支持负权,Floyd 更适合全源最短路,Prim 是求最小生成树的算法,不是求最短路。

Q6. Prim 算法和 Kruskal 算法分别基于哪种核心思想?

单选题 · hard

  • A.加点扩展;按边排序合并← 正确答案
  • B.按边排序合并;加点扩展
  • C.动态规划;分治思想
  • D.随机贪心;暴力枚举

答案:A。Prim 从某个顶点开始,不断把离已连接部分最近的点加进来,本质是“加点扩展”。Kruskal 把所有边按权值从小到大排序,再逐个合并并查集,本质是“按边合并”。两者都是贪心思想,不是动态规划或随机算法。

Q7. 哪些情况下拓扑排序一定不存在?(多选)

多选题 · easy

  • A.图中存在有向环← 正确答案
  • B.图中不存在入度为零的点← 正确答案
  • C.图中存在孤立点
  • D.图中包含双向边← 正确答案

答案:ABD。有向环和双向边都构成环,拓扑排序无法输出一个合法的全序。如果一个有向图完全没有入度为零的点,说明每个任务都有前置依赖,必然存在环,也无法拓扑排序。孤立点入度为零,不影响拓扑排序。

Q8. 邻接表存储图有哪些主要优点?(多选)

多选题 · mid

  • A.空间只与顶点数和边数相关← 正确答案
  • B.遍历某个顶点的邻居时效率高← 正确答案
  • C.任意两点之间邻接判断最快
  • D.便于在稀疏图上做 BFS 和 DFS← 正确答案

答案:ABD。邻接表只存与顶点真实相连的边,空间约为 \(V+E\),遍历邻居时能直接拿到邻居列表,适合稀疏图。但判断任意两点是否相邻需要遍历邻居列表,不如邻接矩阵快。

Q9. 关于包含负权边的图,下列说法正确的是?(多选)

多选题 · hard

  • A.无负环时 Bellman-Ford 可求最短路径← 正确答案
  • B.有负环时不存在有限最短路径← 正确答案
  • C.Dijkstra 算法一定无法使用
  • D.Floyd 可以处理无负环负权图← 正确答案

答案:ABD。Bellman-Ford 要求没有负环,因为负环会让路径无限减小。Floyd 同样要求无负环。Dijkstra 虽然在部分负权图里碰巧正确,但缺乏保证,所以不能说“一定无法使用”,只能说它不可靠。

Q10. 顶点数 \(10^5\)、边数 \(2 \times 10^5\) 的带权稀疏图,求单源最短路哪种方案最优?

单选题 · extreme

  • A.优先队列优化 Dijkstra← 正确答案
  • B.Floyd 算法
  • C.朴素 Dijkstra 扫描数组
  • D.直接枚举所有路径

答案:A。稀疏图用邻接表加堆优化 Dijkstra,复杂度 \(O(E\log V)\) 完全可承受。Floyd 是 \(O(V^3)\),朴素 Dijkstra 是 \(O(V^2)\),面对十万顶点都会超时;枚举路径更是指数爆炸。