想象你是这座城市新上任的供水总调度。城市东边是水库(源点),西边是拥有千万居民的片区(汇点),脚下埋着密密麻麻的管道,每根粗细不同(容量)。你的考核指标只有一个:单位时间内,让尽可能多的水从水库送到居民区。这就是“最大流问题”——它不只存在于水管里,还藏在任务分配、通信调度、甚至 AI 训练的带宽优化中。 怎么调度最聪明?老师傅们给了你三套方案。老实巴交的调度员:每次只找“泵站最少”的一条路送水,送不动就换一条,一条一条地试(EK 算法,Edmonds-Karp)。分层指挥官:先把整个管网按“离水库的远近”分成一层层,再在同一层之间同时开闸放水,推不动了重新分层(Dinic 算法)。经验老到的老师傅:一眼看出哪些层已经没水可送,直接绕过死路,还记住每个泵站上次送水的出水管,绝不重复试错(ISAP 算法)。 三种方案各有门道,这篇文章会带你从给水管网走进数学建模,看懂三者的复杂度差异,最后在真实工程里选对算法、写出能跑的代码。
最大流是图论与组合优化的核心模块,对 AI 从业者而言,它贯穿二分图匹配、资源调度与分布式训练优化等场景,是算法功底与工程嗅觉的双重试金石。
目录
可视化演示
最大流问题有多种经典算法,Edmonds-Karp简单易懂基于BFS,ISAP通过间隙优化与当前弧优化提升效率... 切换模式查看不同维度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
最大流算法概:核心原理 动态展示关键流程与数据流动 交互控件调节参数实时渲染 f(x) = transform(x) 核心概念的可视化呈现
问题定义:给水管网建模
作为供水总调度,你面对的其实是一张“有方向的管网图”:水库只有一个出口,用 S 表示源点;居民区只有一个总入口,用 T 表示汇点;中间每个泵站是一个节点,每根管道是一条有向边。管道粗细决定了水的上限,数学上称为“容量”。你的目标,是算出从 S 到 T 每秒最多能送多少吨水。把复杂管网抽象成图模型后,这个问题就有了精确的数学表达。
在图论中,最大流问题定义为:给定有向图 $G=(V,E)$,每条边 $(u,v)$ 有一个非负容量 $c(u,v)$,指定源点 $s$ 与汇点 $t$,求从 $s$ 出发能够到达 $t$ 的最大流量。除了容量限制,水流还必须满足“守恒定律”——中间节点既不能凭空产水,也不能偷偷存水,流进来的必须等于流出去的。正是这两条规则,把“送水问题”变成了一个可计算的优化模型。
把管网“翻译”成这套符号之后,问题就清晰了:所有算法都在回答同一个问题——如何在容量和守恒的双重约束下,逼近甚至严格达到最大流量。接下来出场的三种算法,就是三种性格迥异的调度方案,它们的效率差异,正是这篇文章要讲的重点。
- 目标函数最大化从源点 $s$ 流向所有邻接点的流量之和,也就是整个网络的“出口总流量”
- 容量约束任意边 $(u,v)$ 上的流量 $f(u,v)$ 必须夹在 0 和容量 $c(u,v)$ 之间,管道不能超载
- 流量守恒对任何非源非汇的中间节点 $u$,流入总和等于流出总和,水不在节点处积压
EK算法:老实调度员的最短增广
最朴素的调度员是怎么干的?他手上有一张管网全图,每次送水前,先用最笨也最稳的办法:从水库出发,逐层向外扫描,找到一条“经过泵站数量最少”的路线,然后按下这条路的最大通水量放水。等这条路上有管道满了,再回头重新扫描,换下一条路。这个“逐层扫描找最短路线”的动作,计算机里叫 BFS(广度优先搜索)。这种每次只送一条路、反复 BFS 的算法,就是 Edmonds-Karp 算法,简称 EK。
EK 的核心秘密在于“残余网络”。你可能会想:管道送满了水,不就废了吗?其实不然。算法会给每条边记两笔账:正向记录“还剩多少容量”,反向记录“已经送了多少,可以撤回”。例如管道 $u \to v$ 容量 10,已经送了 7,那么正向容量剩 3,同时多出一条虚拟的“反向管道” $v \to u$ 容量 7。这些正向与反向的剩余容量合在一起,组成残余网络。它给了算法“反悔”的机会——如果后面发现更好的路线,可以把之前送的水退回去重新安排。每一次沿残余网络中的一条路增加流量,称为一次“增广”。
老实调度员的好处是绝不会错——BFS 保证每次都走全局最短路,正确性有严密证明;缺点是效率看运气。如果管道网络设计得刁钻,他可能要来回跑很多趟。但在小规模管网上,这种“老实”恰恰是优点:实现简单、不易写错,是很多工程新手首选的方案。
- 残余容量边 $(u,v)$ 上还能容纳的流量,等于原容量减去已用流量;这是判断“能不能再送水”的唯一依据
- BFS 最短路增广在残余网络中,用 BFS 寻找边数最少的 $s \to t$ 路径,并沿这条路推入“瓶颈容量”
- 总复杂度每次增广至少让一条边饱和,最坏增广 $O(VE)$ 次;每次 BFS 遍历全部边 $O(E)$,相乘得 $O(VE^2)$
Dinic算法:分层指挥官的多路推送
比老实调度员更聪明的,是一位“分层指挥官”。他不满足于一次只送一条路,而是先做整体规划:用 BFS 从水库出发,把整个管网按“到源点最少经过几个泵站”分成若干层。比如水库自己是第 0 层,直接相连的泵站是第 1 层,再远一点是第 2 层……水流的规则被限定为“只准从第 $k$ 层流向第 $k+1$ 层”,不许横跳或回流。这张只保留层间前进边的图,就是“分层图”。
分层之后,指挥官派出 DFS(深度优先搜索)在层间反复探路,一口气找出所有能走的路径,把当前分层结构下能送的流量全部推满,直到达到“阻塞流”——即无论怎么走都无法再从源点推水到汇点。然后重新 BFS 分层,再推阻塞流,循环往复。关键在于:每轮分层后,汇点的“层号”至少增加 1,所以最多分层 $O(V)$ 轮;而每轮内部通过 DFS 多路增广,总复杂度降下来了。这就是 Dinic 算法。
分层指挥官的高明之处,是把“全局最短路径”升级成了“全局分层 + 局部多路推送”。他不再纠结于哪条路绝对最短,而是先把地图按距离切开,再在每一层内同时开闸。这种“分而治之”的思路,让 Dinic 在绝大多数场景下比 EK 快一个量级,也让它成为算法竞赛和工程实践中的默认选择。
- 分层规则从源点 BFS,若残余边 $(u,v)$ 容量大于 0,则 $v$ 的层号等于 $u$ 的层号加 1;层号只增不减,保证路径单调向前
- 阻塞流推送在分层图上反复 DFS 增广,直到本层网络中不存在任何 $s \to t$ 路径,这时的流量集合称为阻塞流
- 轮数上界每轮分层后,汇点 $t$ 的层号严格增加,因此最多 $O(V)$ 轮;每轮 DFS 找阻塞流开销 $O(VE)$,乘积得 $O(V^2E)$
ISAP算法:老练师傅的双项优化
如果说 Dinic 是科班出身的指挥官,ISAP 就是摸爬滚打多年的老师傅。他沿用了“距离标号”的思路——从汇点反向 BFS 一次,给每个节点标上“到汇点还有多少步”,记为 $d(v)$。此后他不再重新分层,而是在增广的同时动态维护这些距离标号。每次只从当前节点 $v$ 走向满足 $d(v) = d(u) + 1$ 的邻点 $u$,保证每一步都走在“离汇点更近一步”的正确方向上。这是第一项优化:只做一次反向 BFS,省去反复分层。
老师傅还有两个小本本。第一个本子记录“当前弧”:每个泵站上次送水的出水管是哪一根,下次直接从这根之后开始试,绝不重复检查已经饱和的管道,这叫“当前弧优化”;第二个本子记录“间隙”:统计每一层距离标号各有多少个节点。一旦发现某一层一个节点都没有($gap[i] = 0$),说明网络已经“断层”——水流不可能从源点够到汇点了,立即收工。这个“间隙优化”让算法能提前终止,省掉大量无效试探。这两招合起来,就是 ISAP(Improved Shortest Augmenting Path)的完整形态。
老师傅的经验换来了两个看得见的好处:单次 BFS 初始化后不再全局重扫,加上当前弧优化减少了大量重复边检查,常数极小。它的最坏复杂度和 Dinic 同为 $O(V^2E)$,但实战中常常比 Dinic 快上 10% 到 30%。当然,代价是实现细节更繁琐——距离标号更新、当前弧指针复位、gap 数组维护,每处都得小心翼翼。
- 距离标号初始化从汇点反向 BFS,$d(v)$ 表示节点 $v$ 到汇点 $t$ 的最短边数;这是后续所有前进决策的“指南针”
- 前进规则只沿 $d(v) = d(u) + 1$ 的边从 $v$ 走向 $u$,保证每一步都严格逼近汇点;找不到这样的边时,再更新 $d(v)$ 并重试
- 间隙优化维护 $gap[i]$ 表示距离标号为 $i$ 的节点数,一旦 $gap[i] = 0$,说明 $s$ 到 $t$ 之间出现不可跨越的断层,算法直接结束
性能对比:三张成绩单
把三位调度员放到同一张成绩单上,最直观的对比是“最坏时间复杂度”:EK 是 $O(VE^2)$,Dinic 和 ISAP 都是 $O(V^2E)$。单看渐近记号,后两者全面占优,但问题没这么简单——不同的图结构会改写排名。在二分图最大匹配这类特殊网络上,Dinic 的时间可以降到 $O(E\sqrt{V})$,比通用复杂度整整低一个量级;而在单位容量网络(每条边容量都是 1)上,Dinic 的复杂度进一步优化为 $O(E \min(V^{2/3}, \sqrt{E}))$。EK 虽然没有这些“隐藏技能”,但它在顶点数很小、边数很多时,$VE^2$ 未必比 $V^2E$ 差。
空间开销方面,三者几乎平手,都是 $O(V+E)$ 级别的存储:邻接表加少量辅助数组。真要抠常数,EK 最省,因为它只需要 BFS 队列和父节点数组;Dinic 多一张分层图;ISAP 多一套距离标号和 gap 数组,但差距都在几十 KB 以内。实际表现上,有个经验规律:边数 $E$ 远大于顶点数 $V$ 时,$VE^2$ 会迅速膨胀,Dinic/ISAP 优势明显;顶点数大而边稀疏时,EK 的常数优势会缩小差距,甚至反超。
成绩单看完,结论清晰:没有绝对的王者,只有合适的选手。EK 简单稳健、易于调试,是教学和小规模图的首选;Dinic 综合实力最强,适用面最广;ISAP 常数最优,是大规模流量问题的冲刺型选手。工程上选谁来“送水”,得看管网的规模、形状和你的调试耐心。
- 一般图上界Dinic 与 ISAP 在任意有向图上最坏均为 $O(V^2E)$,这是理论分析的“保底成绩”
- 二分图特例在二分图匹配中,Dinic 的复杂度降为 $O(E\sqrt{V})$,因为分层轮数被匹配结构显著压缩
- 单位容量特例当所有边容量为 1 时,Dinic 进一步优化到 $O(E \min(V^{2/3}, \sqrt{E}))$,这是竞赛题“网格图大流量”场景的重要结论
工程选型:按图纸挑方案
回到供水调度的日常:这张管网图纸到底该派哪位调度员上场?经验法则可以浓缩成一条公式——总耗时约等于“增广轮数 × 每轮平均成本”。EK 的轮数上界是 $O(VE)$,但每轮只有一个简单的 BFS,成本极低;Dinic 和 ISAP 的轮数上界是 $O(V)$,但每轮要做分层或维护距离标号,成本更高。图小的时候,EK 的“多跑几趟”完全不成问题,反而因为代码短、好维护而胜出;图一大,轮数差距被放大,就必须上 Dinic 或 ISAP。
具体的选型标尺可以按顶点数划定:顶点数 $V \leq 500$ 时,EK 足够,十分钟写完还不容易错;$V$ 到 $10^4$ 级别、边数中等,Dinic 是稳妥之选,它平衡了实现复杂度与运行速度;$V$ 和 $E$ 都很大、且对耗时极度敏感(比如在线评测的极限数据),ISAP 值得你付出额外的实现成本。若是二分图匹配、任务分配这类特殊结构,优先套用 Dinic 的 $O(E\sqrt{V})$ 特例,代码反而更简单。
选型从来不是一道数学题,而是一道工程权衡题。把管网图纸摊开,看清规模与结构,再决定派谁上场——这份判断力,正是你在真实项目中从“会写算法”迈向“会选算法”的关键一步。顺带一提,编译器的指令调度、寄存器分配等底层优化,同样会化归为网络流模型,这套选型思路在系统底层同样适用。
- 总耗时分解一个算法的总耗时约等于所有增广轮次的成本之和;这个分解式是选型思考的起点
- 轮数对比EK 的增广轮数上界 $O(VE)$,Dinic/ISAP 仅 $O(V)$;顶点数越大,轮数差距越致命
- 经验标尺按顶点规模给出粗略建议——小图上 EK 的简单性胜出,中大型图交给 Dinic/ISAP
AI 应用场景
数据标注任务的二分图匹配
阶段:数据准备与标注管理
解决的问题:传统方式用人工排班,把标注员随机分配到任务上,经常出现“这个标注员不会做该任务”或“任务积压、人力闲置”的错配。将“标注员擅长哪些任务”建模为一个二分图,左侧是标注员、右侧是任务,擅长关系就是边,求最大匹配即可得到最优分配方案。
具体案例:某 CV 团队有 30 名标注员、200 个视频片段任务,每人只擅长目标框、语义分割、3D 点云标注中的部分类型。将擅长关系建模为二分图后,用 Dinic 算法求解最大匹配,毫秒级得到分配结果,闲置率下降约 20%,任务积压明显缓解。
分布式训练中的通信带宽调度
阶段:模型训练与集群优化
解决的问题:多 GPU 并行训练时,梯度同步要占用大量通信带宽,而不同 GPU 之间的链路带宽(NVLink、PCIe、InfiniBand)各异。把 GPU 视为节点、链路视为带容量边,求从“参数服务器”到各工作节点的最大流,即可得到最优的梯度聚合路径规划。
具体案例:8 卡训练集群中,NVLink 带宽远高于 PCIe,直接按顺序同步会浪费高速链路。将带宽建模为流网络后,用 ISAP 求解最大流,梯度同步耗时降低了约 15%,训练吞吐显著提升。
Python 演示
# 最大流 Dinic 算法实现class Dinic: def __init__(self, n): self.n = n self.adj = [[] for _ in range(n)] # 邻接表 def add_edge(self, u, v, cap): # 正向边记录反向边的下标 self.adj[u].append([v, cap, len(self.adj[v])]) self.adj[v].append([u, 0, len(self.adj[u]) - 1]) def bfs(self, s, t): self.level = [-1] * self.n queue = [s] self.level[s] = 0 for u in queue: for e in self.adj[u]: if e[1] > 0 and self.level[e[0]] < 0: self.level[e[0]] = self.level[u] + 1 queue.append(e[0]) return self.level[t] >= 0 def dfs(self, u, t, f): if u == t: return f while self.it[u] < len(self.adj[u]): e = self.adj[u][self.it[u]] if e[1] > 0 and self.level[u] + 1 == self.level[e[0]]: ret = self.dfs(e[0], t, min(f, e[1])) if ret > 0: e[1] -= ret self.adj[e[0]][e[2]][1] += ret return ret self.it[u] += 1 return 0 def max_flow(self, s, t): flow = 0 while self.bfs(s, t): self.it = [0] * self.n while True: f = self.dfs(s, t, float('inf')) if f == 0: break flow += f return flow if __name__ == '__main__': # 构造示例网络: 0=S, 1=A, 2=B, 3=T dinic = Dinic(4) dinic.add_edge(0, 1, 10) dinic.add_edge(0, 2, 5) dinic.add_edge(1, 2, 5) dinic.add_edge(1, 3, 10) dinic.add_edge(2, 3, 10) print(dinic.max_flow(0, 3)) # 输出 15
self.adj[u].append([v, cap, len(self.adj[v])])建边时同时记录反向边在邻接表中的下标,这是后续更新反向容量的“钩子”self.level[e[0]] = self.level[u] + 1BFS 给邻点分层,层号递增是 Dinic 保证路径单调向前的关键if e[1] > 0 and self.level[u] + 1 == self.level[e[0]]:只在残余容量大于 0 且层号 +1 的边上走,绝不横跳或后退self.it[u] += 1当前弧优化:这条边已经试过了,下次直接从下一条开始,避免重复检查饱和边self.adj[e[0]][e[2]][1] += ret更新反向边容量,相当于给水流留出“反悔通道”while self.bfs(s, t):每轮重新分层,直到汇点不可达说明最大流已求出flow += f把每一轮阻塞流的总增量累加进最终答案
常见误区
误区我以为 EK 性能差是 BFS 搜索太慢导致的,只要把 BFS 换成 DFS 就能提速,算法本质没区别。
正解EK 的真正瓶颈不在 BFS 本身,而在“每次只增广一条最短路,增广次数高达 $O(VE)$”。BFS 只是实现工具,换成 DFS 反而可能找到很长的增广路,破坏最短路性质,甚至导致算法无法保证在多项式时间内终止。用供水比喻说:调度员跑得再快,也架不住一趟只送一条路、来回跑几千趟。提升效率的关键是减少轮数,而不是换交通工具。
为何常见:初学者容易把“搜索方式”与“增广策略”混为一谈,看到 BFS 和 DFS 的直观差异,就误以为性能差距来自搜索本身。
误区我觉得 ISAP 优化措施更多,理论上应该吊打 Dinic,所以实战中永远选 ISAP 就对了。
正解两者最坏复杂度同为 $O(V^2E)$,实际表现取决于图结构。ISAP 用单次反向 BFS 初始化距离标号并动态维护,在部分图上遍历边数更少、常数更小;Dinic 的分层图结构简单,实现稳健,在分层清晰的图上同样很快。二者互有胜负,不是简单的“优化多者必胜”。
为何常见:把“优化措施的多少”误当成“性能的绝对排序”,忽略了算法效率是复杂度上界、常数因子和具体图结构三者博弈的结果。
误区我认为反向边只是给算法“纠错”用的后门,如果一开始选路够聪明,根本不需要它。
正解反向边不是补丁,而是残余网络这一理论模型的必要组成部分。它让算法能对已分配的流量重新规划,这正是最大流正确性证明的核心。没有反向边,贪心策略会过早地“堵死”最优路线。好比供水调度中,某条干管先送满了水,后来发现调一部分水走另一条干线能让总流量更大——这时“把水退回去”的能力不是纠错,而是优化的基本手段。
为何常见:直觉上“水流只能单向”,让人难以接受网络中还存在可撤回的反向容量,而这恰恰是残余网络最精妙的设计。
误区我以为最大流就是一个讲水管运输的玩具模型,真实工作中根本用不上。
正解最大流是组合优化的通用语言:二分图最大匹配、任务调度、图像分割中的最小割、编译器寄存器分配、社交网络社区划分等都能建模为流网络。最小割最大流定理更是连接“流量”与“切割”两大问题的桥梁。可以说,凡是有“容量限制 + 供需分配”的地方,就有最大流的身影。
为何常见:教材习惯用供水管网的例子引入,学生容易把载体当成本质,没有意识到“带容量边的有向图”这一抽象模型可以承载无数具体问题。
面试题
Q1. EK 算法每次寻找增广路时使用的搜索方式是?
- A.DFS 深度优先搜索
- B.BFS 广度优先搜索← 正确答案
- C.随机游走
- D.贪心选择容量最大的边
答案:B。EK 算法全称 Edmonds-Karp,核心特征是每次用 BFS 在残余网络中寻找“边数最少”的增广路,以此保证增广次数有上界。DFS 无法保证最短性,随机与贪心更是与正确性无关。就像老实调度员每次必须走“泵站最少”的路,这是 EK 正确性的前提。
Q2. 在最大流模型中,除源点和汇点外,任何中间节点必须满足什么条件?
- A.流入量大于流出量
- B.流出量大于流入量
- C.流入量等于流出量← 正确答案
- D.流量恰好等于容量
答案:C。这是流量守恒定律:中间节点既不能产水也不能存水,流入必须等于流出。类比供水管网,泵站只是中转站,水进来多少就必须出去多少。A 和 B 意味着节点在积压或凭空产水,违背物理直觉,D 则是混淆了容量约束与守恒约束。
Q3. 对于一个有 V 个顶点、E 条边的图,EK 算法的最坏时间复杂度是?
- A.O(VE)
- B.O(V²E)
- C.O(VE²)← 正确答案
- D.O(E√V)
答案:C。EK 每轮 BFS 需要 $O(E)$ 时间,而最多进行 $O(VE)$ 次增广,相乘得到 $O(VE²)$。$O(V²E)$ 是 Dinic 和 ISAP 的复杂度,$O(E√V)$ 是 Dinic 在二分图匹配特例下的复杂度。区分这几组公式是最大流面试的入门题。
Q4. 关于 Dinic 算法的核心思想,下列说法正确的是?
- A.使用 BFS 构建分层图← 正确答案
- B.在分层图上用 DFS 多路增广← 正确答案
- C.每次只找一条增广路
- D.用间隙优化提前终止
答案:AB。Dinic 的两大支柱是 BFS 分层和 DFS 推阻塞流,一次分层后可以多路增广。C 是 EK 的特征,D 是 ISAP 才有的间隙优化。分层指挥官的智慧在于“整体规划 + 层层推满”,而不是一条路走到底。
Q5. 残余网络中的反向边主要作用是?
- A.增加网络的总容量
- B.让算法能反悔并重新规划流量← 正确答案
- C.加快 BFS 的搜索速度
- D.减少顶点的数量
答案:B。反向边是残余网络的理论核心,它代表“已分配的流量可以被撤回并重新规划”,这是最大流算法正确性的关键。它不会增加原图容量,也不影响搜索速度或顶点数量。供水调度中,反向边就是那条允许“退水重调”的隐形管道。
Q6. 相比 Dinic,ISAP 引入了哪些优化?
- A.当前弧优化← 正确答案
- B.间隙优化← 正确答案
- C.只用一次反向 BFS 初始化距离标号← 正确答案
- D.每轮重新构建分层图
答案:ABC。ISAP 的三板斧是当前弧优化、间隙优化和单次反向 BFS 初始化距离标号。D 是 Dinic 的做法,ISAP 恰恰省去了每轮全局重新分层。老师傅的“小本本”——当前弧指针和 gap 数组——让它在实战中常数更小。
Q7. 在二分图最大匹配问题中,使用 Dinic 算法的时间复杂度是?
- A.O(V²E)
- B.O(VE²)
- C.O(E√V)← 正确答案
- D.O(E + V)
答案:C。二分图匹配是最大流的经典特殊场景,Dinic 的复杂度可以显著下降到 $O(E√V)$。一般图上的 $O(V²E)$ 上界仍然成立,但二分结构使分层轮数被压缩。记住这个特例会让你在面试时多一张“隐藏技能牌”。
Q8. 在一般有向图中,Dinic 与 ISAP 的最坏时间复杂度均为下面哪个?
- A.O(VE²)
- B.O(V²E)← 正确答案
- C.O(E√V)
- D.O(V²)
答案:B。两者最坏情况下都需要 $O(V)$ 轮,每轮 $O(VE)$,所以上界是 $O(V²E)$。A 是 EK 的上界,C 是 Dinic 在二分图上的特例,D 缺少边数因子。注意“最坏”二字,实战中两者往往远快于此上界。
Q9. 为什么 ISAP 不需要像 Dinic 那样每一轮都重新做一次全局 BFS 分层?
- A.因为它在增广后动态维护距离标号,只在必要时局部修正← 正确答案
- B.因为它使用堆优化的 Dijkstra 替代 BFS
- C.因为它只处理容量为整数的图
- D.因为它一次遍历所有路径后算法立即终止
答案:A。ISAP 只做一次反向 BFS 初始化距离标号,增广过程中通过“前进失败时更新当前节点标号”的方式局部维护,距离标号始终有效。Dijkstra 用于求最短路,与流量增广无关;容量整数性和遍历所有路径都不是跳过分层的理由。这是 ISAP 常数小的核心原因。
Q10. 在单位容量网络(每条边容量均为 1)中,关于 Dinic 算法复杂度的描述,正确的有?
- A.复杂度可以降低到 O(E√E)
- B.复杂度表示为 O(E·min(V^(2/3), sqrt(E)))← 正确答案
- C.最坏情况仍不超过 O(V²E)← 正确答案
- D.复杂度一定优于 EK 的 O(VE²)
答案:BC。单位容量网络中 Dinic 的复杂度可以优化为 $O(E \min(V^{2/3}, \sqrt{E}))$,这是理论界的重要结论,B 正确。虽然特例更优,但最坏情况的通用上界 $O(V²E)$ 仍然成立,C 正确。A 的错误在于 $\sqrt{E}$ 缺少与 $V^{2/3}$ 取 min 的结构,D 的“一定优于”过于绝对,复杂度上界比较还要结合常数与具体图。