Archipelago

Lec 10深度优先搜索

讲义完成更新于 2026-09-16

Lec 10 深度优先搜索

资料依据:课程视频与 transcript · Lecture notes

TL;DR

  • DFS 沿一条未访问边尽可能深入,再回溯处理其他分支;整个遍历依然只需 O(∣V∣+∣E∣)O(|V|+|E|) 时间。
  • 对所有未访问顶点启动 DFS 可得到一片森林,并用完成顺序揭示连通性与依赖结构。
  • 有向图在无环时,顶点按 DFS 退出时间的逆序构成拓扑序;遇到指向当前递归栈的边则证明存在环。

背景: 可到达性问题

深度优先搜索图示 1

策略:设 P(s) = None, 然后运行 visit(s)

visit(u):
	for every v ∊ Adj(u):
		if P(v) = None:
			Set P(v) = u
			Call visit(v)

DFS

  • 类似于 BFS,从 s 开始搜索图
  • 解决单源可达性问题,而不是单源最短路径问题(sP)
  • 返回指向 s 的父指针树
  • 思路是: 递归访问出边的邻接顶点,但不重新访问同一个顶点,即沿着任何路径前进直到无法前进,然后回溯直到找到一条未探索的路径继续探索

正确性证明

  • 断言: DFS 访问顶点 v 并正确设置 P(v),对于从 s 可达的每个顶点 v 都成立
  • 证明: 对 k 进行归纳,只对距离 s 为 k 的顶点进行断言
    • 基本情况(k=0): P(s) 被正确设置为 s 并被 s 访问
    • 归纳步骤,考虑顶点 v 满足δ(s,v)=k′+1\delta(s, v) = k' + 1
    • 考虑顶点 u,即从 s 到 v 的某条最短路径上的倒数第二个顶点(即 v 前一个顶点)
    • 根据归纳假设,由于δ(s,u)=k′\delta(s,u) = k', DFS 访问 u 并正确设置 P(u)
    • 在访问 u 时,DFS 考虑v∈Adj(u)v \in Adj(u)要么已经在 P 中,已被访问(即 P(u) ≠\ne null),要么 v 会在访问 u 时被访问,无论那种情况, v 都被 DFS 访问并正确添加到 P 中

分析

  • 算法最多访问每个顶点一次,并且对于每个顶点v∈Adj(u)v \in Adj(u) 只花费 O(1)时间
  • 工作量的上界为∑u∈VO(1)×deg(u)=O(∣E∣)\sum_{u∈V}O(1)×deg(u)=O(∣E∣)
  • 不同于 BFS, DFS 并不返回每个顶点的距离,因此 DFS 的运行时间为 O(|E|)

全量 BFS/DFS

  • 假设我们想要搜索整个图,而不仅仅是从一个顶点可达的顶点
  • 思路是,对任务未访问的顶点重复执行图搜索算法 A
  • 重复以下步骤,知道所有顶点都被访问
    • 选择一个未访问的顶点 s,使用 A 搜索从 s 可达的所有顶点
  • 我们称这个算法为 Full-A 算法
  • 每个顶点访问一次,所以 Full-BFS 和 Full-DFS 的运行时间均为 O(|V|+|E|)

图的连通性

  • 一个无向图是连通的,如果图中每对顶点之间都有一条路径连接。
  • 在有向图中,顶点 u 可能从顶点 v 可达,但 v 可能从 u 不可达。
  • 对于有向图,连通性更加复杂(我们在本课中不会讨论)。
  • Connectivity(G):无向图 G 是否连通?
  • Connected Components(G):给定无向图 G = (V, E),返回 V 的划分为子集 Vi⊆VV_i \subseteq V(连通分量),其中每个 ViV_i 在 G 中是连通的,并且不同连通分量的顶点之间没有边。
  • 考虑一个解决单源可达性问题的图算法 A。
  • 断言:A 可以用来解决连通分量问题。
  • 证明:运行 Full-A。对于每次运行 A,将访问到的顶点放入一个连通分量中。

拓扑排序

  • 有向无环图(DAG,Directed Acyclic Graph)是一个不包含有向环的有向图
  • 图 G = (V, E) 的拓扑排序是对顶点的一个排序,使得每条边(u,v)∈E(u, v) \in E满足 f(u)<f(v)f(u) \lt f(v)
  • 拓扑排序不是唯一的。
  • 证明一个有向图, 当且仅当它是一个 DAG 时,才存在拓扑排序。(练习)
  • 如何找到拓扑排序?
    • 结束顺序是全量 DFS 完成访问每个顶点的顺序
    • 断言: 如果 G=(V, E)是一个 DAG,则结束顺序的逆序是一个拓扑排序
    • 证明: 需要证明对每条边而言, u 被排到 v 之前,即访问 v 完成的时间在访问 u 之前。分两种情况
      • 如果 u 在 v 之前被访问
        • 在访问 u 完成之前,会访问 v(通过边(u, v)或者其他方式)。
        • 因此访问 v 完成的时间在访问 u 之前
      • 如果 v 在 u 之前被访问
        • 由于图是无环的,u 不能从 v 过来
        • 因此访问 v 完成的时间在 u 之前

循环检测

如果图 G=(V, E)是无环的,Full-DFS 将找到一个拓扑排序,那如果 Full-DFS 的逆结束顺序不是拓扑排序,则 G 必定包含一个环。

  • 检查 G 是否无环: 对于每条边(u, v),检查在逆结束顺序中,v 是否在 u 之前
  • 可以通过哈希表或者是直接访问数组在 O(|E|)时间内完成
  • 要返回这样的一个环,在 Full-DFS 中维护路径上回到 s 的前驱集合
  • 断言: 如果 G 包含一个环,Full-DFS 将遍历从 v 到 v 的前驱的边
  • 证明: 考虑 G 中有一个环(v0,v1,...vk,v0)(v_0, v_1, ... v_k, v_0)
    • 不失一般性地,假设v0v_0是 Full-DFS 在环上访问的第一个顶点,对于每个viv_i,在访问viv_i完成之前,将访问vi+1v_{i+1}并完成访问,考虑边(vi,vi+1)(v_i, v_{i+1}),如果vi+1v_{i+1}尚未被访问,他将访问被现在访问。因此在访问v0v_0完成之前,将第一次访问vkv_k(根据对v0v_0的假设),所以在访问vkv_k完成之前,将考虑边(vk,v0)(v_k, v_0),其中v0v_0是vkv_k的前驱

我的理解

我的理解

BFS 保留的是距离层次,DFS 保留的是嵌套时间区间。后者不直接给无权最短路,却能把“先完成谁”变成拓扑顺序和环检测的证据。