【数据结构】图的遍历:深度优先搜索(DFS)

考点频率 :★★★★★(数据结构必考,选择题常考DFS序列,下午题偶尔结合图考查)

难度 :⭐⭐⭐

建议:重点掌握DFS的递归实现思路,理解其"一条路走到黑"的回溯思想,能模拟DFS的顶点访问顺序

1️⃣ 什么是图的遍历?

图的遍历 是指从图中的某个顶点出发,按照某种策略访问图中的所有顶点 ,且每个顶点只被访问一次

图的结构比树复杂------树有根节点,从根出发可以到达所有节点;但图没有根,而且可能存在环路,如果不小心就会重复访问同一个顶点。因此,图的遍历需要额外记录哪些顶点已经被访问过了。

图的遍历主要有两种方式:

遍历方式 核心思想 数据结构
深度优先搜索(DFS) 一条路走到黑,走不通再回头 (递归本质上是栈)
广度优先搜索(BFS) 层层推进,先近后远 队列

这两篇文章我们采用对比学习的方式:DFS和BFS非常容易混淆,放在一起对比能帮你理清它们的不同。DFS更像是"走迷宫"------你不确定哪条路能到终点,只能先选择一个方向一直往下走,走到死路再退回来换一条路。

2️⃣ 深度优先搜索(DFS)的核心思想

深度优先搜索(Depth-First Search,DFS) 的策略是:

"从起点出发,选择一个未被访问过的邻接顶点,一直往下走;走到没有未访问的邻接顶点时,就回退到上一个顶点,继续探索其他分支。"
打个比方 :DFS就像走迷宫。你站在入口处,选一条岔路一直走到头,发现是死路,就退回到最近的一个岔路口,换一条路继续走。你永远不回头去走那些还没探索过的岔路,直到把所有的路都走完。这个过程就叫**"回溯(Backtracking)"**------走不通就退回来,换条路再走。

3️⃣ DFS的算法步骤

伪代码(递归实现):

c 复制代码
void DFS(Graph G, int v) {
    visited[v] = TRUE;        // 标记当前顶点已访问
    Visit(v);                 // 访问顶点v(如打印)
    
    for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
        if (!visited[w]) {    // 如果邻接顶点w未被访问
            DFS(G, w);        // 递归访问w
        }
    }
}

非递归实现(使用栈):

  1. 将起始顶点入栈,标记为已访问
  2. 当栈不为空时:
    • 取出栈顶顶点并访问
    • 将该顶点的所有未被访问的邻接顶点入栈,并标记为已访问

4️⃣ DFS的遍历过程示例

对下面的无向图进行DFS遍历(从顶点1开始,按编号升序选择邻接顶点):

复制代码
    1 ------ 2
    | \  |
    |  \ |
    4 ------ 3

邻接关系:
1 → 2, 3, 4
2 → 1, 3
3 → 1, 2, 4
4 → 1, 3

执行过程

步骤 操作 栈(递归调用栈) 已访问顶点 说明
初始 访问1 [1] {1} 从1开始
1 1的邻接中选最小的未访问顶点2 [1, 2] {1, 2} 1→2
2 2的邻接中选最小的未访问顶点1(已访问),选3 [1, 2, 3] {1, 2, 3} 2→3
3 3的邻接中1已访问、2已访问、4未访问 [1, 2, 3, 4] {1, 2, 3, 4} 3→4
4 4的邻接1已访问、3已访问,无未访问顶点 [1, 2, 3] {1, 2, 3, 4} 回溯到3
回溯 3无其他未访问顶点 [1, 2] {1, 2, 3, 4} 回溯到2
回溯 2无其他未访问顶点 [1] {1, 2, 3, 4} 回溯到1
回溯 1无其他未访问顶点 [] {1, 2, 3, 4} 遍历结束

DFS访问顺序1 → 2 → 3 → 4

5️⃣ DFS的深度优先生成树

在DFS遍历过程中,第一次访问某个顶点时经过的边 会形成一棵树,称为深度优先生成树(DFS Spanning Tree)

还是用上面的图,DFS访问顺序为 1 → 2 → 3 → 4,经过的边有:

  • 1 → 2(首次访问2)
  • 2 → 3(首次访问3)
  • 3 → 4(首次访问4)

这棵树以1为根,向下生长出2、3、4。其余边(如1→3、1→4、2→3)没有被包含在树中,它们被称为回边(Back Edge) ,指向已经访问过的顶点。

图的DFS生成树不唯一------取决于邻接顶点的访问顺序。如果每次选择邻接顶点时顺序不同(如从编号大的先访问),生成树的形态也会不同。

6️⃣ DFS的时间复杂度与空间复杂度

存储方式 时间复杂度 空间复杂度
邻接矩阵 O(n2)O(n^2)O(n2)(需要遍历n行,每行n列) O(n)O(n)O(n)(递归栈深度)
邻接表 O(n+e)O(n + e)O(n+e)(每个顶点访问一次,每条边遍历两次) O(n)O(n)O(n)(递归栈深度)

分析:邻接表的DFS时间复杂度优于邻接矩阵,因为邻接矩阵需要检查每个顶点的所有n个位置来判断邻接关系,而邻接表只遍历实际存在的边。

7️⃣ DFS的典型应用

应用场景 说明
判断图的连通性 一次DFS能访问到的顶点都在同一个连通分量中
求连通分量个数 对每个未被访问的顶点调用DFS,调用的次数就是连通分量数
拓扑排序 对有向无环图进行DFS,后序序列的反序为拓扑排序
检测图中是否有环 在DFS过程中,如果遇到已访问但不是父节点的顶点,说明有环
迷宫寻路 DFS是走迷宫最直接的思路

8️⃣ 经典例题

例题1:对下面的图进行DFS遍历(从顶点A开始,按字母顺序选择邻接顶点),请写出遍历序列。

复制代码
A ------ B ------ D
|   /    |
|  /     |
C ------ --------- E

解析

  • 从A出发,邻接顶点为B、C(按字母顺序先B)
  • A→B,B的邻接为A、C、D(A已访问,先C)
  • B→C,C的邻接为A、B、E(A、B已访问,E未访问)
  • C→E,E的邻接为B、C、D(B、C已访问,D未访问)
  • E→D,D的邻接为B、E(都已访问)
  • 所有顶点都已访问,结束

答案A → B → C → E → D


例题2:在有n个顶点、e条边的连通无向图中,DFS遍历过程中会形成一棵生成树,该生成树有( )条边。

A. n

B. n-1

C. n+1

D. e

解析 :生成树包含n个顶点,连通且无环,有n-1条边。选 B

9️⃣ 记忆口诀

DFS用栈(递归),一条路走到黑。

访问标记防止重,走到尽头就回溯。

邻接表 O(n+e)O(n+e)O(n+e),邻接矩阵 O(n2)O(n^2)O(n2)。

🔟 小测验(评论区对答案)

对下面的图进行DFS遍历(从顶点1开始,按编号从小到大选择邻接顶点),访问顺序为( )。

复制代码
    1 ------ 2
   /|\   |
  / | \  |
 3  4  5 6

A. 1 → 2 → 3 → 4 → 5 → 6

B. 1 → 2 → 3 → 4 → 5 → 6

C. 1 → 3 → 2 → 4 → 5 → 6

D. 1 → 2 → 6 → 3 → 4 → 5

🔔 本专栏日更,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容

#软考中级 #软件设计师 #深度优先搜索 #DFS #图遍历 #数据结构 #软考备考

相关推荐
-凌凌漆-2 小时前
【freertos】Task创建(v2)
java·开发语言·算法
Nil2082 小时前
leetcode 24两两交换链表中的节点
算法·leetcode·链表
.格子衫.3 小时前
033动态规划之状态压缩DP——算法备赛
算法·动态规划
智者知已应修善业3 小时前
【51单片机数码管逐位显示1-8余位不亮】2024-10-23
c语言·经验分享·笔记·嵌入式硬件·51单片机
Wang's Blog3 小时前
PostgreSQL笔记16: 索引基础——概念、类型、创建与最佳实践
数据库·笔记·postgresql
ysa0510303 小时前
c++常用自带函数用法与注意
c++·笔记·算法
无聊的菜鸟3 小时前
TMS320F2806X学习笔记(一)—— 了解片上资源
笔记·ti·c2000·f2806x
带多刺的玫瑰4 小时前
Leecode#4刷题之寻找两个正序数组的中位数
java·前端·算法