假设你是一名刚上岗的跑腿小哥,手里拿着城市地图,要从仓库出发给散落在各处的顾客送餐。地图上每条马路都标了预计耗时,你的任务是找到从仓库到每个顾客家最快的走法。最笨的办法是把所有走法都试一遍,但路口一多,组合数量会爆炸到连计算机都扛不住。今天要讲的 Dijkstra 和 Floyd,就是两种“聪明的地图算法”:Dijkstra 像一位急性子跑腿小哥,永远先处理眼前预计耗时最短的路口;Floyd 像一张全城速查表,把每个路口轮流当成中转站试一遍。还有一位 Bellman-Ford 像夜间巡逻车,专门识破“越走越便宜”的倒贴路段骗局。读完这篇,你会理解导航软件、网络路由和物流调度背后到底在算什么。

岗位关联

对 AI 从业者来说,最短路算法是路径规划、物流调度、网络优化等智能系统的地基。能根据数据规模与约束正确选型,是算法工程师必备的决策力。

目录
  1. 可视化演示
  2. 最短路问题
  3. 贪心策略:Dijkstra 的核心
  4. 堆优化:让 Dijkstra 跑得更快
  5. 动态规划:Floyd 的全源速查表
  6. 负权检测:Bellman-Ford 的补救
  7. 复杂度对比:三个方案的性价比
  8. 应用选型:现实中怎么选
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

Dijkstra贪心逐步确定单源最短路径,Floyd动态规划更新全源距离矩阵,直观对比两种经典图算法的执行过程。

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

Dijkstra单源最短路径 绿色=已确定最短路径的节点 黄色=待确定,显示当前估计距离 贪心策略:每次选最小距离的未确定节点 优先队列优化复杂度O((V+E)logV)

最短路问题

跑腿小哥小张的地图其实可以抽象成一张“带权图”:路口是图的顶点,道路是边,道路上的预计耗时就是边的权重。如果一个路口和另一个路口之间没有直接的路,我们就认为它们之间没有边;如果道路是单行道,就把它画成有方向的边。这样,任何一条送餐路线都对应图里的一条路径,路径的总耗时就是把沿途每条边的权重加起来。

最短路问题要回答的就是:在所有从起点 s 到终点 v 的路径中,哪一条的总权重最小?这个最小总权重记作 d(s,v)。

如果每条边的权重都正好是 1,那问题就退化成无权图最短路,直接用广度优先搜索 BFS 就能在 O(V+E) 时间内解决。但真实地图中每条路耗时不同,所以需要更通用的算法。先弄清楚是只求一个起点到所有点,还是要求任意两点之间,才能决定该请哪一位算法出场。

d(s,v)=min⁡P∈P(s,v)∑e∈Pw(e)\displaystyle d(s,v)=\min_{P\in\mathcal{P}(s,v)}\sum_{e\in P} w(e)
这个公式的意思是:先列出所有可行路径,再计算每条路径的总耗时,最后挑出最小的那一个。
  1. 最短距离符号d(s,v) 代表从起点 s 到顶点 v 的候选最短距离,初始时只有 s 是 0,其他都是无穷大。
    d(s,v)d(s,v)
  2. 路径集合\mathcal{P}(s,v) 是所有从 s 到 v 的可行路径的集合,每条路径可能经过不同的中转路口。
    P(s,v)\mathcal{P}(s,v)
  3. 路径总权把路径 P 上每条边的权重 w(e) 累加起来,就得到这条路径的总耗时。
    ∑e∈Pw(e)\sum_{e \in P} w(e)
  4. 取最小值在所有可行路径里挑总权重最小的那一个,这就是最短路。
    min⁡P∈P(s,v)∑e∈Pw(e)\min_{P \in \mathcal{P}(s,v)} \sum_{e \in P} w(e)

贪心策略:Dijkstra 的核心

小张的直觉很朴素:既然每条路耗时都不会是负数,那么已经被确认最短距离的那个路口,以后无论怎么绕路都不可能变得更短。所以他准备了一张距离表,起点写 0,其他路口暂时写“无穷大”;每次从还没确认的路口里挑距离最小的那个,把它从“待定”改成“确定”,然后沿着它的每条出路,看看能否让邻居的距离变得更小。这一步在算法里叫作“松弛”。

Dijkstra 的贪心之处,就是永远相信“当前距离最小的待定节点”已经拿到了最终答案。这个直觉只在边权非负时成立,因为任何绕路都会增加总耗时,不可能偷出更短的路径。

小张按这个规则一步步处理下去,等所有路口都变成“确定”状态时,距离表里就是仓库到全城每个路口的最短耗时。贪心策略虽然简单,但胜在每一步都建立在“边权非负”这个坚实前提上。

relax(u,v):d[v]=min⁡(d[v], d[u]+w(u,v))\displaystyle \text{relax}(u,v):\quad d[v]=\min(d[v],\,d[u]+w(u,v))
松弛操作就是问一句:从 u 绕一下去 v,会不会比原来记录的 d[v] 更快?如果会,就更新距离表。
  1. 当前距离d[u] 是已经确定的起点到 u 的最短距离,d[v] 是 v 当前的候选距离。
    d[u], d[v]d[u],\ d[v]
  2. 经过 u 的新距离从 u 出发经过边 (u,v) 走到 v,总耗时是 d[u] 加上这条边的权重。
    d[u]+w(u,v)d[u]+w(u,v)
  3. 取较小者把新方案和旧的 d[v] 比较,只保留更小的值,这就是一次松弛。
    min⁡(d[v], d[u]+w(u,v))\min(d[v],\ d[u]+w(u,v))
  4. 写回距离表若新方案更短,就用新值覆盖 d[v],同时把 v 的候选状态更新到待处理队列中。
    d[v]=min⁡(d[v], d[u]+w(u,v))d[v]=\min(d[v],\,d[u]+w(u,v))

堆优化:让 Dijkstra 跑得更快

如果不做任何优化,小张每次都要在所有“待定”路口中扫描一遍找最小值,这就像把整个电话簿从头翻到尾只为找一个电话号码。当城市路口很多时,这种 O(V^2) 的做法会明显变慢。堆优化就是给小张配一个“叫号机”:所有待定路口按距离排成小顶堆,堆顶永远是最小值。

优先队列保存的是“候选距离 + 路口编号”。每松弛一条边,就把新的候选距离压进堆里;同一个路口可能被压入多次,所以取出堆顶后要先检查一下:如果取出的距离比距离表里当前记录的值大,说明这条记录已经过期,直接丢弃。这个技巧叫“惰性删除”。

经过堆优化后,Dijkstra 的时间复杂度从朴素版的 O(V^2+E) 变成 O((V+E)\log V)。在道路稀疏的大城市里,E 远小于 V^2,这种优化能让导航系统从“慢吞吞查电话簿”变成“秒响应叫号机”。

u=arg⁡min⁡v∈Qd[v]\displaystyle u=\arg\min_{v\in Q} d[v]
堆优化做的事,就是让“从候选中寻找最小距离节点”这一步从 O(V) 降到了 O(log V)。
  1. 候选集合Q 是所有尚未正式确认最短距离的路口集合。
    QQ
  2. argmin 取最小\arg\min 表示“能够使 d[v] 最小的那个 v”,也就是小张下一秒要去处理的路口。
    arg⁡min⁡v∈Qd[v]\arg\min_{v\in Q} d[v]
  3. 堆实现工程上用最小堆维护 (d[v], v),堆顶自然就是当前最小距离的节点。
    heap top=(d[v],v)\text{heap top} = (d[v], v)

动态规划:Floyd 的全源速查表

公司看小张效率高,又给他派了个新任务:做一张全城速查表,让任意两个路口之间都能直接查到最短耗时。这时 Dijkstra 虽然也能做,但每次只能从一个起点出发;如果路口很多,反复跑 Dijkstra 也很贵。Floyd 算法的思路是用动态规划,把每个路口轮流当作“临时中转站”。

设 D_k[i][j] 表示“只允许经过前 k 个路口作为中转”时,i 到 j 的最短距离。考虑第 k 个路口时,从 i 到 j 要么完全不经过 k,要么经过 i→k→j。于是可以不断用中转点 k 来刷新整张表。

实现时用邻接矩阵初始化:i 到 i 的距离是 0,有直接边的点对填权重,没边就填无穷大。外层循环必须按 k 从小到大进行,让每个中转点只“上任”一次。等所有 k 都处理完,矩阵里就是任意两点之间的最短距离。Floyd 可以处理负权边,但不能有负权环;如果最后发现 dist[i][i] 变成负数,说明图里藏着一个“倒贴钱”的循环。

Dk[i][j]=min⁡(Dk−1[i][j], Dk−1[i][k]+Dk−1[k][j])\displaystyle D_k[i][j]=\min\bigl(D_{k-1}[i][j],\,D_{k-1}[i][k]+D_{k-1}[k][j]\bigr)
Floyd 的核心递推:不停问“如果绕道 k 中转,会不会更近?”每轮用一个新中转点更新所有点对。
  1. 子问题定义D_k[i][j] 表示只允许编号 1 到 k 的路口作为中转时,i 到 j 的最短距离。
    Dk[i][j]D_k[i][j]
  2. 不经过 k原来不用 k 做中转时的答案就是 D_{k-1}[i][j]。
    Dk−1[i][j]D_{k-1}[i][j]
  3. 经过 k如果绕道 k,需要把 i 到 k 和 k 到 j 两段距离加起来。
    Dk−1[i][k]+Dk−1[k][j]D_{k-1}[i][k]+D_{k-1}[k][j]
  4. 取最小值两种方案中选更短的那个,作为允许前 k 个中转点时的最优答案。
    min⁡(Dk−1[i][j], Dk−1[i][k]+Dk−1[k][j])\min\bigl(D_{k-1}[i][j],\,D_{k-1}[i][k]+D_{k-1}[k][j]\bigr)

负权检测:Bellman-Ford 的补救

小张某天遇到一条奇怪的打折路段:经过它不扣时间,反而倒找时间。普通地图当然不会有这种路,但在金融套利、游戏数值等场景中,负权边是真实存在的。边权为负时,Dijkstra 的贪心结论会失效,因为一个路口可能先被确定距离,之后又从一条负权小路绕回来变得更短。

Bellman-Ford 算法不搞“每次选最近”的贪心,而是像夜班巡逻车一样,老老实实把每条边都扫一遍,连续扫 V-1 轮。为什么是 V-1 轮?因为一条不绕圈的简单路径最多包含 V-1 条边,信息沿着路径最多传播 V-1 次就能到达所有点。

如果完成 V-1 轮之后,再扫一遍所有边仍能让某个 d[v] 变小,那就说明存在负权环——因为在这个环里每绕一圈总耗时都会下降,最短路径根本不存在。Bellman-Ford 的时间复杂度是 O(VE),虽然比 Dijkstra 慢,但它能接手 Dijkstra 不敢碰的负权场景。

d(k)[v]=min⁡(d(k−1)[v], min⁡(u,v)∈E(d(k−1)[u]+w(u,v)))\displaystyle d^{(k)}[v]=\min\Bigl(d^{(k-1)}[v],\ \min_{(u,v)\in E}\bigl(d^{(k-1)}[u]+w(u,v)\bigr)\Bigr)
第 k 轮松弛后,距离表里保存的是“最多经过 k 条边”的最短距离。V-1 轮之后如果还能松弛,就说明存在负权环。
  1. 轮数上标d^{(k)}[v] 表示经过 k 轮扫描后,起点到 v 的当前最短距离。
    d(k)[v]d^{(k)}[v]
  2. 所有入边对 v 的每一条入边 (u,v),考虑从 u 过来是否更短。
    min⁡(u,v)∈E(d(k−1)[u]+w(u,v))\min_{(u,v)\in E}\bigl(d^{(k-1)}[u]+w(u,v)\bigr)
  3. 新旧比较把“绕道 u 再到 v”和上一轮的旧值比较,留下更小者。
    min⁡(d(k−1)[v], min⁡(u,v)∈E(d(k−1)[u]+w(u,v)))\min\Bigl(d^{(k-1)}[v],\ \min_{(u,v)\in E}\bigl(d^{(k-1)}[u]+w(u,v)\bigr)\Bigr)

复杂度对比:三个方案的性价比

现在小张手里有三套方案,选哪一套要看图的规模。Dijkstra 适合单源、非负、稀疏图;Floyd 适合全源、点数较少或边很密的图;Bellman-Ford 适合需要处理负权边或检测负环的场景。

空间上,Dijkstra 和 Bellman-Ford 只需要保存距离表 O(V),Floyd 需要保存整个二维矩阵 O(V^2)。所以 Floyd 虽然能直接给出全源答案,但顶点数上千时内存也会很紧张。选算法不是选“最聪明的”,而是选“性价比最高的”。

TDijkstra=O((V+E)log⁡V),TFloyd=O(V3),TBellman-Ford=O(VE)\displaystyle T_{\text{Dijkstra}}=O((V+E)\log V),\quad T_{\text{Floyd}}=O(V^3),\quad T_{\text{Bellman-Ford}}=O(VE)
时间成本从快到慢大致是:稀疏单源用 Dijkstra,稠密全源用 Floyd,负权场景只能用 Bellman-Ford。
  1. V 和 EV 是顶点数,E 是边数,这是衡量图规模的两个基本单位。
    V, EV,\ E
  2. log V 的来源堆优化 Dijkstra 每次取出和插入堆都要花费 O(log V)。
    log⁡V\log V
  3. O(V^3) 与 O(VE)Floyd 的三重循环导致 O(V^3);Bellman-Ford 是 V-1 轮乘上 E 条边,所以是 O(VE)。
    V3, VEV^3,\ VE

应用选型:现实中怎么选

回到小张的公司:如果只是从仓库给顾客送餐,那就跑一趟 Dijkstra;如果想知道全国所有城市中转站两两之间的最低运输成本,那就用 Floyd;如果某些路线允许“补贴价”导致负权,那就必须让 Bellman-Ford 先检查负环。选择算法就像选交通工具:去楼下便利店走路就行,跨城要坐高铁,运重货得叫卡车。

导航系统里常用的 A* 算法就是在 Dijkstra 的基础上加了一个“朝着终点方向优先扩展”的启发式,让搜索更有方向感。而在物流系统中,如果城市数量不多但关系很密,Floyd 的一次矩阵计算能省下大量重复查询时间。选型的关键从来不是“背复杂度”,而是先回答:单源还是全源?有没有负权?图是稀疏还是稠密?

choice={Dijkstra单源、非负、稀疏Floyd全源、点数少或稠密Bellman-Ford存在负权、无负环\displaystyle \text{choice}=\begin{cases}\text{Dijkstra} & \text{单源、非负、稀疏}\\\text{Floyd} & \text{全源、点数少或稠密}\\\text{Bellman-Ford} & \text{存在负权、无负环}\end{cases}
真实系统里,地图导航通常用 Dijkstra 或其加速版 A*;网络路由协议 OSPF 用 Dijkstra,RIP 用 Bellman-Ford;物流全源成本表常用 Floyd。
  1. 单源场景只从一个起点出发计算最短路,优先选择堆优化的 Dijkstra。
    Dijkstra\text{Dijkstra}
  2. 全源场景要求任意两点之间的最短距离,且顶点数不大时,Floyd 简单直接。
    Floyd\text{Floyd}
  3. 负权场景存在负权边时用 Bellman-Ford,并先用最后一轮检查是否出现负环。
    Bellman-Ford\text{Bellman-Ford}

AI 应用场景

地图导航与外卖调度

阶段:路径规划与实时决策

解决的问题:传统穷举所有路线在路口多时会指数爆炸,AI 系统中的导航模块需要毫秒级计算从当前位置到目标点的最优路线。Dijkstra 搭配优先队列和启发式搜索,可以在实时路况下快速重算路线。

具体案例:外卖平台在高峰期接到新订单后,系统会把路网建模成带权图,每条路的权重来自实时车速和红绿灯等待时间,然后运行 Dijkstra 或 A* 找到骑手与商家、顾客之间的最短路线。

网络路由协议

阶段:链路状态感知与网络自动收敛

解决的问题:互联网路由器需要快速知道“把数据包从当前节点送到目标节点最短路径是哪条”。传统手工配置无法适应拓扑变化,需要分布式算法自动更新路由表。

具体案例:OSPF 是典型的链路状态协议,每个路由器收集全网拓扑后用 Dijkstra 计算最短路径树;RIP 则用类似 Bellman-Ford 的思路不断交换距离向量,二者共同支撑着企业网络和数据中心的自愈能力。

物流配送的全源成本表

阶段:全局运筹优化

解决的问题:物流公司有几十个中转场,希望提前算好任意两个中转场之间的最低运输成本。如果每次都单独跑单源算法,查询会重复且低效;Floyd 一次性生成全源距离表更适合这种稠密小规模网络。

具体案例:某快递公司把全国 50 个核心中转场的运输时效整理成邻接矩阵,用 Floyd 计算出两两之间最短时效表,后续做干线调度和运费报价时直接查表,查询时间降到 O(1)。

Python 演示

python47 行
import heapq def dijkstra(graph, src):    n = len(graph)    dist = [float('inf')] * n    dist[src] = 0    pq = [(0, src)]    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 def floyd(graph):    n = len(graph)    INF = float('inf')    dist = [[INF] * n for _ in range(n)]    for i in range(n):        dist[i][i] = 0        for v, w in graph[i]:            dist[i][v] = w    for k in range(n):        for i in range(n):            for j in range(n):                if dist[i][k] + dist[k][j] < dist[i][j]:                    dist[i][j] = dist[i][k] + dist[k][j]    return dist if __name__ == "__main__":    graph = [        [(1, 2), (2, 5)],        [(2, 1), (3, 6)],        [(4, 3)],        [(4, 2), (5, 3)],        [(5, 1)],        []    ]    print("Dijkstra from 0:", dijkstra(graph, 0))    all_pairs = floyd(graph)    print("Floyd dist matrix:")    for row in all_pairs:        print(row)
目的:用 Python 同时实现 Dijkstra 和 Floyd,验证同一张图上单源最短路与全源最短路的计算结果。
  1. dist = [float('inf')] * n初始化距离表,把所有顶点到起点的距离先设为无穷大。
  2. pq = [(0, src)]优先队列从起点开始,起点距离为 0。
  3. if d > dist[u]: continue这是惰性删除,跳过已经过期的堆记录。
  4. nd = d + w计算经过 u 再到 v 的候选新距离。
  5. for k in range(n):Floyd 外层循环,k 是当前允许使用的中转点。
  6. if dist[i][k] + dist[k][j] < dist[i][j]:检查绕道 k 是否比 i 到 j 原来的路更短。

常见误区

误区既然 Dijkstra 能快速求出最短路径,那遇到负权边时应该也能用,只是结果可能不太准,但大体上没问题。

正解Dijkstra 的贪心正确性完全依赖“边权非负”。一旦出现负权边,一个已经被标记为确定的路口,可能被一条负权小路绕回来变得更短。比如 s→a 耗时 5,s→b 耗时 5,b→a 耗时 -10,那么先确认 a 就会得到错误答案。所以只要存在负权边,就必须改用 Bellman-Ford 或 Floyd。

为何常见:很多人先学 Dijkstra,习惯了“每次选最近点”的直觉,没有意识到这个直觉建立在非负权重的隐含假设上。

误区Floyd 就是一个三重循环,外层是 i 还是 k 应该无所谓,反正最后都会把矩阵刷一遍。

正解外层循环必须是“中转点 k”,不能换成 i 或 j。因为 D_k 的定义是“只允许前 k 个点中转”,如果 k 不是最外层,就可能在一个中转点还没被完整考虑时提前使用它,导致结果漏掉某些路径。形象地说,你不能让一家还没开业的商场作为中转站。

为何常见:Floyd 的代码看起来只是三个 for 嵌套,初学者容易忽略动态规划的阶段顺序,只记住“三重循环”这个表面特征。

误区Bellman-Ford 既然支持负权,那负权环应该也只是让答案更小,算法照样能跑完。

正解负权环会让最短路失去定义。只要进入这个环,每绕一圈总耗时都变小,理论上可以无限循环下去。Bellman-Ford 的做法不是“求出答案”,而是检测并报告“负权环存在”。因此在实际系统中,发现负权环后需要先修正数据或调整业务规则。

为何常见:把“算法能跑完”误当成“算法有正确输出”,忽略了无界下降在数学上根本没有最小值。

误区最短路就是经过路口最少的路,所以 BFS 就能解决所有最短路问题。

正解最短路比较的是“总权重”,不是“边数”。一条绕一点的高速路可能比一条直穿老城区的路快得多。只有当所有权重都为 1 时,最短路才等价于最少边数,这时才适合用 BFS。

为何常见:日常生活中人们常说“走最近的路”,容易把“路程近”和“路边少”混为一谈,忽略了每条路的权重并不相同。

面试题

Q1. Dijkstra 算法最适合解决哪类问题?

单选题 · easy

  • A.单源最短路径,且边权非负← 正确答案
  • B.全源最短路径,且边权可为负
  • C.带负权环的最短路径
  • D.任意含负权边的图

答案:A。Dijkstra 依赖贪心策略,只有在所有边权非负时,才能保证一旦确定某个节点的距离就不会再被更新。负权边和负权环会让贪心失效,所以选 A。

Q2. Dijkstra 每一步选择哪个节点进行扩展?

单选题 · easy

  • A.当前距离起点最近且尚未固定的节点← 正确答案
  • B.距离终点最近的节点
  • C.当前边权最小的边
  • D.入度最大的节点

答案:A。Dijkstra 从“待定集合”中选出 d 值最小的节点固定。它选的是“当前已知距起点最近”的节点,而不是离终点最近,也不是边权最小的边。

Q3. Floyd 算法的核心思想是什么?

单选题 · easy

  • A.动态规划,用中转点逐步松弛所有点对← 正确答案
  • B.贪心,每次选当前最短边
  • C.分治递归
  • D.随机采样逼近

答案:A。Floyd 用 D_k[i][j] 表示只允许前 k 个点中转时的最短路,每轮用一个新中转点更新矩阵。它属于动态规划,不是贪心或分治。

Q4. 一个图存在负权边但不存在负权环,要求单源最短路径,应该选哪个算法?

单选题 · mid

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

答案:C。Dijkstra 不能处理负权边;BFS 只适合无权图;Floyd 适合全源。Bellman-Ford 可以处理负权边并在最后检测负权环,所以选 C。

Q5. Bellman-Ford 检测负权环的时机是?

单选题 · mid

  • A.初始化距离表之前
  • B.完成 V-1 轮松弛后,再扫描一遍所有边查看是否还能松弛← 正确答案
  • C.每轮松弛开始时
  • D.优先队列为空时

答案:B。简单路径最多有 V-1 条边,因此 V-1 轮后距离应稳定。若第 V 轮仍能松弛,说明存在负权环,Bellman-Ford 在此时报告检测结果。

Q6. 使用优先队列优化后的 Dijkstra 时间复杂度是?

单选题 · mid

  • A.O(V^3)
  • B.O(VE)
  • C.O((V+E)log V)← 正确答案
  • D.O(V+E)

答案:C。堆优化让每次取最小和入堆都耗 O(log V),每条边最多松弛一次,每个节点可能多次入堆,所以总复杂度是 O((V+E)log V)。

Q7. 在无负权边的稀疏图上求全源最短路径,最合适的做法是?

单选题 · hard

  • A.直接运行一次 Floyd
  • B.对每个节点运行堆优化 Dijkstra← 正确答案
  • C.对每个节点运行 Bellman-Ford
  • D.直接运行 BFS

答案:B。稀疏图 E 接近 V,Floyd 的 O(V^3) 不划算;每点跑 Dijkstra 约为 O(V E log V),在稀疏图上明显更优。Bellman-Ford 更慢,BFS 不能处理带权图。

Q8. 关于 Floyd 算法,下列说法正确的是?

多选题 · mid

  • A.外层循环 k 必须是中转点← 正确答案
  • B.初始化时把 dist[i][i] 设为 0← 正确答案
  • C.能正确处理含负权环的图
  • D.空间复杂度是 O(V^2)← 正确答案

答案:ABD。Floyd 的动态规划阶段要求 k 在最外层,对角线距离为 0,矩阵空间 O(V^2)。它不能“求出”负权环上的最短路,但可用对角线负数检测负环,因此 C 错误。

Q9. 哪些情况下不能直接使用 Dijkstra?

多选题 · mid

  • A.存在负权边← 正确答案
  • B.存在负权环← 正确答案
  • C.稀疏无权图
  • D.图中没有任何边

答案:AB。Dijkstra 正确性依赖边权非负,因此负权边和负权环都不能用。稀疏无权图可以用 BFS,但 Dijkstra 也能跑;无边图所有距离都是无穷大,同样可以处理。

Q10. 关于 Bellman-Ford 算法,正确的描述有?

多选题 · extreme

  • A.每一轮都要扫描图中所有边← 正确答案
  • B.简单最短路径最多包含 V-1 条边← 正确答案
  • C.第 V 轮仍可松弛说明存在负权环← 正确答案
  • D.时间复杂度是 O(V^3)

答案:ABC。Bellman-Ford 每轮扫描所有边,共 V-1 轮;因为简单路径不可能超过 V-1 条边。第 V 轮仍能松弛表示有负环。其复杂度是 O(VE),不是 O(V^3),故 D 错误。