Skip to content

深度优先搜索(DFS)

核心思想

DFS 的核心策略:尽可能深地搜索图的分支,走到死胡同时再回溯。

  • 递归/栈:DFS 天然适合用递归实现,系统调用栈自动完成回溯;也可以用显式栈模拟
  • visited 数组:用 visited[i] 标记顶点 i 是否已被访问,防止重复访问(图中可能有环)
  • 回溯:当前顶点的所有邻接点都已被访问时,沿原路退回到上一个顶点,继续探索其他分支

DFS 的执行过程类似于树的先序遍历:

访问 v → 找到 v 的一个未访问邻接点 w → 访问 w → 继续深入 ...
        → 所有邻接点都已访问 → 回溯到上一层

交互可视化

通过下方的交互动画,你可以逐步观察深度优先搜索的执行过程:

加载可视化中...

操作详解

算法思路

关键步骤:

  1. 初始化 visited[] 数组,所有顶点标记为未访问
  2. 从起始顶点 v 出发,标记 v 为已访问,执行访问操作
  3. 依次检查 v 的所有邻接点 w:
    • 若 w 未被访问,则递归地对 w 执行 DFS
    • 若 w 已被访问,则跳过
  4. 当 v 的所有邻接点都已被访问,回溯到调用 DFS(v) 的上一个顶点
  5. 若图为非连通图,需要对每个连通分量分别调用 DFS(遍历 visited[] 找未访问的顶点)

邻接矩阵实现

c
#define MAX_VERTEX 100
int visited[MAX_VERTEX];
int G[MAX_VERTEX][MAX_VERTEX]; // 邻接矩阵
int n; // 顶点数

void DFS(int v) {
    visited[v] = 1;
    printf("%d ", v); // 访问顶点 v
    for (int w = 0; w < n; w++) {
        if (G[v][w] == 1 && !visited[w]) {
            DFS(w);
        }
    }
}

// 遍历整个图(处理非连通图)
void DFSTraverse() {
    for (int i = 0; i < n; i++)
        visited[i] = 0;
    for (int i = 0; i < n; i++) {
        if (!visited[i])
            DFS(i); // 每次调用产生一棵 DFS 生成树
    }
}

邻接矩阵中查找某个顶点的所有邻接点需要遍历一整行,时间为 O(V),因此总时间复杂度为 O(V²)

邻接表实现

c
typedef struct ArcNode {
    int adjvex;              // 邻接点编号
    struct ArcNode *next;    // 指向下一条边
} ArcNode;

typedef struct {
    int data;                // 顶点数据
    ArcNode *first;          // 指向第一条边
} VNode;

VNode adjList[MAX_VERTEX];
int visited[MAX_VERTEX];
int n;

void DFS(int v) {
    visited[v] = 1;
    printf("%d ", v);
    ArcNode *p = adjList[v].first;
    while (p != NULL) {
        if (!visited[p->adjvex]) {
            DFS(p->adjvex);
        }
        p = p->next;
    }
}

void DFSTraverse() {
    for (int i = 0; i < n; i++)
        visited[i] = 0;
    for (int i = 0; i < n; i++) {
        if (!visited[i])
            DFS(i);
    }
}

邻接表中每条边只被访问一次(无向图两次),总时间复杂度为 O(V+E)

DFS 生成树

对连通图进行 DFS,所有经过的边(即递归调用时使用的边)构成一棵DFS 生成树

  • 连通图:一次 DFS 调用产生一棵生成树
  • 非连通图:多次 DFS 调用产生一个DFS 生成森林,每个连通分量对应一棵生成树
  • 有向图:DFS 生成的是有向生成森林

注意:同一个图的 DFS 生成树不唯一,取决于起始顶点和邻接点的访问顺序。使用邻接矩阵和邻接表得到的 DFS 序列通常不同。

⚠️ 易错:DFS 和 BFS 一样,遍历序列不唯一。邻接表的链表顺序不同会产生不同的 DFS 序列,邻接矩阵则唯一。408 选择题常给出一个图和一个遍历序列,问"这个序列可能是 DFS/BFS 的结果吗"。

复杂度分析

存储方式时间复杂度说明
邻接矩阵O(V²)每个顶点需扫描一整行找邻接点
邻接表O(V+E)每个顶点只访问其邻接边

空间复杂度:O(V)。需要 visited[] 数组(O(V))以及递归调用栈(最坏 O(V),当图退化为链时)。

对比项DFSBFS
数据结构栈(递归调用栈)队列
思想尽可能深入,回溯逐层扩展
时间复杂度(邻接表)O(V+E)O(V+E)
时间复杂度(邻接矩阵)O(V²)O(V²)
空间复杂度O(V)O(V)
最短路径不能求无权图最短路径可以求无权图最短路径
适用场景连通性、路径搜索、拓扑排序最短路径、层次遍历

考研高频考点

  • ⭐ 给定图的邻接矩阵/邻接表,写出 DFS 序列(选择题/填空题高频)
  • ⭐ DFS 与 BFS 的对比:数据结构、适用场景、能否求最短路径(简答题必考)
  • ⭐ DFS 生成树/生成森林的构造(画图题常考)
  • ⭐ 用 DFS 判断图的连通性:无向图连通分量个数 = DFSTraverse 中调用 DFS 的次数
  • 邻接矩阵 vs 邻接表下 DFS 时间复杂度的区别及原因
  • DFS 非递归实现(用显式栈模拟)
  • DFS 的应用:判断有向图是否有环、求连通分量

相关知识

  • DFS 使用(递归调用栈或显式栈),与使用队列BFS 对称——掌握两者的对比是图论基础
  • DFS 可以用于判断图的连通性:对无向图,一次 DFS 能访问到的顶点构成一个连通分量
  • DFS 的变体可以实现拓扑排序(逆后序即为拓扑序)和判断有向图是否有环

真题练习

相关真题(3题)

2020Q6选择题2分

DFS 变形:退出递归前输出顶点可得逆拓扑有序序列

2016Q6选择题2分

图的 DFS 遍历:判断哪个序列不是合法的深度优先序列

2015Q5选择题2分

图的 DFS 遍历:从 v0 出发可得到的不同遍历序列数量