考点频率 :★★★★★(数据结构必考,选择题常考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
}
}
}
非递归实现(使用栈):
- 将起始顶点入栈,标记为已访问
- 当栈不为空时:
- 取出栈顶顶点并访问
- 将该顶点的所有未被访问的邻接顶点入栈,并标记为已访问
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 #图遍历 #数据结构 #软考备考