Skip to content

算法Algorithm

深度优先搜索

施工中 · 内容逐步修订

Depth-first search · DFS

沿未访问边尽可能深入后回溯的图遍历算法。

形式陈述 ​

深度优先搜索(DFS)的输入是有限顶点集及可枚举的后继记录,即有向图的邻接接口;本页允许自环和平行弧,平行弧保留独立身份。无向图则把每条边存成两个反向记录。DFS 依次从尚未发现的顶点启动搜索。访问 u 时先记录发现时间 d[u],随后逐一扫描邻接点;遇到未发现顶点 v,置 π[v]=u 并递归访问 v。当 u 的邻接表全部处理完,记录完成时间 f[u]。父边组成深度优先森林;只从指定源点启动时,则只覆盖其可达部分。

递归调用或等价的显式栈在任一时刻保存一条 DFS 树根到当前顶点的活动路径。对白—灰—黑三色状态,白色尚未发现,灰色已发现但未完成,黑色已经完成。时间区间满足括号定理:任意两个不同顶点的区间

[d[u],f[u]]与[d[v],f[v]]

要么不交,要么一个严格包含另一个;在后一种情形中,外层顶点是内层顶点在 DFS 森林中的祖先。这个嵌套不变量支撑环检测、拓扑排序和许多 low-link 算法。

记原图的顶点数为 n,有向弧数(无向输入则为边数)为 m。在顶点索引和邻接记录访问均为常数成本的 RAM 模型、邻接表表示下,每个顶点发现、完成各一次,完整遍历时每条有向弧扫描一次,无向边扫描两个表项,因此确定性最坏时间为

Θ(n+m),

颜色、父指针、时间戳与最深调用栈共用 O(n) 辅助空间。邻接矩阵版本要为每个访问顶点扫描一整行,最坏为 Θ(n2)。

直觉

DFS 把“稍后继续处理的分支”留在栈帧中,先把当前分支走到底。一个帧不仅保存顶点,还保存下一条待扫描的邻接记录。递归进入白色邻点时,父帧暂停;子调用结束后,父帧从原位置继续。只有最后一条邻接记录也处理完,当前顶点才变黑并出栈。

这说明区间为什么只能嵌套或分离:发现另一个顶点时,若旧调用尚未结束,新调用必须在它返回之前结束;若旧调用已经返回,两段时间便不会重叠。邻接次序会改变树、时间戳和输出顺序,却不会改变从给定源可达的顶点集合。

DFS 的递归栈与回溯

这里不能把“用栈遍历”直接等同于标准 DFS。若一次性把当前点的所有邻点压栈并立即标记,就可能把本应先从某个孩子深入发现的顶点提前登记为兄弟,破坏上述祖先与时间区间的解释。要保留这些性质,显式栈应像图中的调用帧一样,每次只推进一个尚未处理的邻接项。

与按层推进的广度优先搜索相比,DFS 没有距离单调性。它换来的信息是活动路径和退出顺序:灰色顶点恰是当前递归祖先,遇到指向灰色祖先的边便得到了一个有向圈证据。

例子与边界

在有向图

1→2,1→3,2→4,3→4,4→2

中,若 1 的邻接表先列 2,并让发现与完成共用从 1 递增的计时器,依次得到 d[1]=1,d[2]=2,d[4]=3。扫描 4→2 时 2 仍为灰色,这条回边与树路 2⇝4 合成有向圈。随后 4、2 完成,时间分别为 4,5;回到 1 才发现 3,得到 d[3]=6,f[3]=7,f[1]=8。

按顶点 1,2,4,3 的发现顺序,区间分别是 [1,8],[2,5],[3,4],[6,7]:2 与 3 的区间分离,4 的区间包含在 2 内。扫描 3→4 时 4 已为黑色;仅凭“目标已访问”会把一条跨向已完成分支的边误报成回边。这条轨迹也说明必须在扫描邻点前标灰,否则环会使递归反复进入同一个节点。

有向图中,指向白点的是树边,指向灰色祖先(包括自身)的边是回边;自环因而直接给出长度一的有向圈。指向黑点的边要结合时间区间区分前向边与交叉边。有向图无环当且仅当任意完整 DFS 森林都没有回边。无向图的非树边只连接祖先与后代,但扫描父边的反向记录不构成圈;若允许平行边,必须按边 ID 排除那一条父边,因为另一条平行边确实形成长度为二的多重图圈。

DFS 首次找到的路径一般不是最短路:在边 s→a,a→t,s→t 中,若先深入 a,得到两条边的树路,尽管直达边只需一步。递归实现还可能在长度为 n−1 的链上耗尽语言运行时栈;显式栈能控制存储位置,但仍需 Θ(n) 最坏空间。若要精确复现递归版的完成时间,栈帧必须同时保存“邻接表已经扫描到哪里”,而不能只压入顶点编号。

推论与应用

确认有向图无回边后,逆完成序可构造拓扑排序,两次 DFS 可分解强连通分量,发现时间与Low-link 值可识别桥、割点和分量。它们共享遍历骨架,正确性却分别依赖“无回边”“凝聚图次序”或“子树能回到多早”的附加不变量。

发现/完成区间还把 DFS 树的子树映成嵌套区间,树的 Euler Tour 技巧据此支持祖先或子树查询。一般有向图的非树边不会因此消失;只有先明确要查询的是 DFS 森林结构,才能把区间结论用于原图问题。

在单入口 flow graph 中,Lengauer–Tarjan 算法借 DFS 编号组织半支配点,但 DFS parent 只代表首次到达,未必支配子节点。边一旦插删,原有搜索树、时间区间和边分类都可能改变;本页的线性界只描述一次静态遍历,不是动态图维护保证。

参考资料
  • CMU 15-451/651,Lecture 8: Depth First Search and Strong Components,2019年9月19日,§§2–3:DFS 活动栈、边分类与 DAG 逆完成序。

  • Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022,Ch. 20。

  • Jon Kleinberg and Éva Tardos, Algorithm Design, Pearson, 2005,§3.3。

  • Robert Sedgewick、Kevin Wayne,Algorithms,4th ed.,2011,§4.1 Undirected Graphs:递归标记、路径父指针与邻接表遍历成本。

关系图谱29 个相邻概念 · 4 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系