【数据结构与算法】第38篇:图论(二):深度优先搜索(DFS)与广度优先搜索(BFS)

一、图遍历的基本概念

1.1 为什么需要遍历

和树一样,图也需要一种方式"访问"所有顶点。但图可能有环,所以需要标记已访问的顶点,避免重复访问。

1.2 两种遍历方式

遍历方式 核心思想 数据结构
DFS 一条路走到底,回溯 栈(递归)
BFS 一层层向外扩展 队列

二、深度优先搜索(DFS)

2.1 算法思想

从一个顶点出发,访问它的一个邻接点,再访问该邻接点的邻接点......直到无法继续,然后回溯。

示例图

text

复制代码
    0 — 1
    |   |
    2 — 3

从0出发的DFS:0 → 1 → 3 → 2(取决于邻接顺序)

2.2 递归实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// 邻接矩阵图
typedef struct {
    int matrix[MAX_VERTICES][MAX_VERTICES];
    int vertexCount;
} Graph;

// 初始化图
void initGraph(Graph *g, int n) {
    g->vertexCount = n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            g->matrix[i][j] = 0;
        }
    }
}

// 添加边(无向图)
void addEdge(Graph *g, int u, int v) {
    g->matrix[u][v] = 1;
    g->matrix[v][u] = 1;
}

// DFS递归实现
void dfsRecursive(Graph *g, int vertex, int visited[]) {
    visited[vertex] = 1;
    printf("%d ", vertex);
    
    for (int i = 0; i < g->vertexCount; i++) {
        if (g->matrix[vertex][i] == 1 && !visited[i]) {
            dfsRecursive(g, i, visited);
        }
    }
}

// DFS入口
void dfs(Graph *g, int start) {
    int visited[MAX_VERTICES] = {0};
    printf("DFS遍历序列: ");
    dfsRecursive(g, start, visited);
    printf("\n");
}

int main() {
    Graph g;
    initGraph(&g, 4);
    
    addEdge(&g, 0, 1);
    addEdge(&g, 0, 2);
    addEdge(&g, 1, 3);
    addEdge(&g, 2, 3);
    
    dfs(&g, 0);
    
    return 0;
}

运行结果:

text

复制代码
DFS遍历序列: 0 1 3 2 

2.3 非递归实现(显式栈)

c

复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// 栈结构
typedef struct {
    int data[MAX_VERTICES];
    int top;
} Stack;

void initStack(Stack *s) { s->top = -1; }
int isEmpty(Stack *s) { return s->top == -1; }
void push(Stack *s, int val) { s->data[++s->top] = val; }
int pop(Stack *s) { return s->data[s->top--]; }

// DFS非递归
void dfsIterative(Graph *g, int start) {
    int visited[MAX_VERTICES] = {0};
    Stack stack;
    initStack(&stack);
    
    push(&stack, start);
    visited[start] = 1;
    
    printf("DFS(栈)遍历序列: ");
    while (!isEmpty(&stack)) {
        int vertex = pop(&stack);
        printf("%d ", vertex);
        
        for (int i = g->vertexCount - 1; i >= 0; i--) {
            if (g->matrix[vertex][i] == 1 && !visited[i]) {
                visited[i] = 1;
                push(&stack, i);
            }
        }
    }
    printf("\n");
}

三、广度优先搜索(BFS)

3.1 算法思想

从一个顶点出发,先访问它的所有邻接点,再访问这些邻接点的邻接点,一层层向外扩展。

示例图 (同上):

从0出发的BFS:0 → 1 → 2 → 3

3.2 队列实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// 队列结构
typedef struct {
    int data[MAX_VERTICES];
    int front;
    int rear;
} Queue;

void initQueue(Queue *q) { q->front = q->rear = 0; }
int isEmpty(Queue *q) { return q->front == q->rear; }
void enqueue(Queue *q, int val) { q->data[q->rear++] = val; }
int dequeue(Queue *q) { return q->data[q->front++]; }

// BFS
void bfs(Graph *g, int start) {
    int visited[MAX_VERTICES] = {0};
    Queue queue;
    initQueue(&queue);
    
    visited[start] = 1;
    enqueue(&queue, start);
    
    printf("BFS遍历序列: ");
    while (!isEmpty(&queue)) {
        int vertex = dequeue(&queue);
        printf("%d ", vertex);
        
        for (int i = 0; i < g->vertexCount; i++) {
            if (g->matrix[vertex][i] == 1 && !visited[i]) {
                visited[i] = 1;
                enqueue(&queue, i);
            }
        }
    }
    printf("\n");
}

int main() {
    Graph g;
    initGraph(&g, 4);
    
    addEdge(&g, 0, 1);
    addEdge(&g, 0, 2);
    addEdge(&g, 1, 3);
    addEdge(&g, 2, 3);
    
    bfs(&g, 0);
    
    return 0;
}

运行结果:

text

复制代码
BFS遍历序列: 0 1 2 3 

四、完整代码演示(邻接表版本)

c

复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// 邻接表节点
typedef struct EdgeNode {
    int vertex;
    struct EdgeNode *next;
} EdgeNode;

// 顶点节点
typedef struct {
    EdgeNode *first;
} VertexNode;

// 图结构
typedef struct {
    VertexNode vertices[MAX_VERTICES];
    int vertexCount;
} GraphList;

// 初始化
void initGraphList(GraphList *g, int n) {
    g->vertexCount = n;
    for (int i = 0; i < n; i++) {
        g->vertices[i].first = NULL;
    }
}

// 添加边(无向图)
void addEdgeList(GraphList *g, int u, int v) {
    EdgeNode *e1 = (EdgeNode*)malloc(sizeof(EdgeNode));
    e1->vertex = v;
    e1->next = g->vertices[u].first;
    g->vertices[u].first = e1;
    
    EdgeNode *e2 = (EdgeNode*)malloc(sizeof(EdgeNode));
    e2->vertex = u;
    e2->next = g->vertices[v].first;
    g->vertices[v].first = e2;
}

// DFS递归(邻接表)
void dfsListRecursive(GraphList *g, int vertex, int visited[]) {
    visited[vertex] = 1;
    printf("%d ", vertex);
    
    EdgeNode *p = g->vertices[vertex].first;
    while (p != NULL) {
        if (!visited[p->vertex]) {
            dfsListRecursive(g, p->vertex, visited);
        }
        p = p->next;
    }
}

void dfsList(GraphList *g, int start) {
    int visited[MAX_VERTICES] = {0};
    printf("DFS(邻接表): ");
    dfsListRecursive(g, start, visited);
    printf("\n");
}

// 队列
typedef struct {
    int data[MAX_VERTICES];
    int front, rear;
} Queue;

void initQueue(Queue *q) { q->front = q->rear = 0; }
int isEmpty(Queue *q) { return q->front == q->rear; }
void enqueue(Queue *q, int val) { q->data[q->rear++] = val; }
int dequeue(Queue *q) { return q->data[q->front++]; }

// BFS(邻接表)
void bfsList(GraphList *g, int start) {
    int visited[MAX_VERTICES] = {0};
    Queue q;
    initQueue(&q);
    
    visited[start] = 1;
    enqueue(&q, start);
    
    printf("BFS(邻接表): ");
    while (!isEmpty(&q)) {
        int vertex = dequeue(&q);
        printf("%d ", vertex);
        
        EdgeNode *p = g->vertices[vertex].first;
        while (p != NULL) {
            if (!visited[p->vertex]) {
                visited[p->vertex] = 1;
                enqueue(&q, p->vertex);
            }
            p = p->next;
        }
    }
    printf("\n");
}

int main() {
    GraphList g;
    initGraphList(&g, 4);
    
    addEdgeList(&g, 0, 1);
    addEdgeList(&g, 0, 2);
    addEdgeList(&g, 1, 3);
    addEdgeList(&g, 2, 3);
    
    dfsList(&g, 0);
    bfsList(&g, 0);
    
    return 0;
}

运行结果:

text

复制代码
DFS(邻接表): 0 2 3 1 
BFS(邻接表): 0 2 1 3 

注意:由于邻接表使用头插法,边的顺序与添加顺序相反,所以遍历序列与邻接矩阵版本可能不同。


五、DFS与BFS的对比

5.1 遍历序列对比

以图 0-1, 0-2, 1-3, 2-3 为例:

遍历方式 序列(邻接矩阵) 序列(邻接表头插)
DFS 0,1,3,2 0,2,3,1
BFS 0,1,2,3 0,2,1,3

5.2 核心区别

对比项 DFS BFS
数据结构 栈(递归) 队列
空间复杂度 O(树高) O(最大宽度)
最短路径 不保证 保证(无权图)
连通分量 可找出 可找出
环检测 可检测 可检测
拓扑排序 适用 适用

5.3 时间复杂度

存储方式 DFS BFS
邻接矩阵 O(V²) O(V²)
邻接表 O(V+E) O(V+E)

六、应用场景

应用 推荐 原因
迷宫寻路(最短路径) BFS 第一次到达即为最短
拓扑排序 DFS 后序输出
检测环 DFS 递归栈可追踪路径
连通分量 DFS/BFS 均可
二分图检测 BFS 按层染色
强连通分量 DFS Tarjan/Kosaraju算法
深度优先的搜索(如八皇后) DFS 需要回溯

七、完整代码(带图构建和双遍历)

c

复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// 图结构(邻接表)
typedef struct EdgeNode {
    int vertex;
    struct EdgeNode *next;
} EdgeNode;

typedef struct {
    EdgeNode *first;
} VertexNode;

typedef struct {
    VertexNode vertices[MAX_VERTICES];
    int vertexCount;
} Graph;

// 初始化
void initGraph(Graph *g, int n) {
    g->vertexCount = n;
    for (int i = 0; i < n; i++) {
        g->vertices[i].first = NULL;
    }
}

// 添加边
void addEdge(Graph *g, int u, int v) {
    EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));
    e->vertex = v;
    e->next = g->vertices[u].first;
    g->vertices[u].first = e;
    
    e = (EdgeNode*)malloc(sizeof(EdgeNode));
    e->vertex = u;
    e->next = g->vertices[v].first;
    g->vertices[v].first = e;
}

// DFS
void dfs(Graph *g, int v, int visited[]) {
    visited[v] = 1;
    printf("%d ", v);
    
    EdgeNode *p = g->vertices[v].first;
    while (p) {
        if (!visited[p->vertex]) {
            dfs(g, p->vertex, visited);
        }
        p = p->next;
    }
}

void startDFS(Graph *g, int start) {
    int visited[MAX_VERTICES] = {0};
    printf("DFS: ");
    dfs(g, start, visited);
    printf("\n");
}

// BFS
void bfs(Graph *g, int start) {
    int visited[MAX_VERTICES] = {0};
    int queue[MAX_VERTICES];
    int front = 0, rear = 0;
    
    visited[start] = 1;
    queue[rear++] = start;
    
    printf("BFS: ");
    while (front < rear) {
        int v = queue[front++];
        printf("%d ", v);
        
        EdgeNode *p = g->vertices[v].first;
        while (p) {
            if (!visited[p->vertex]) {
                visited[p->vertex] = 1;
                queue[rear++] = p->vertex;
            }
            p = p->next;
        }
    }
    printf("\n");
}

int main() {
    Graph g;
    initGraph(&g, 6);
    
    // 构建图
    addEdge(&g, 0, 1);
    addEdge(&g, 0, 2);
    addEdge(&g, 1, 3);
    addEdge(&g, 2, 4);
    addEdge(&g, 3, 5);
    addEdge(&g, 4, 5);
    
    startDFS(&g, 0);
    bfs(&g, 0);
    
    return 0;
}

运行结果:

text

复制代码
DFS: 0 2 4 5 3 1 
BFS: 0 2 1 4 3 5 

八、小结

这一篇我们学习了图的两种遍历方式:

遍历 核心 数据结构 应用
DFS 深入到底,回溯 栈(递归) 拓扑排序、环检测、连通分量
BFS 层层扩散,按层 队列 最短路径(无权图)、二分图

关键代码模板

c

复制代码
// DFS模板
void dfs(Graph *g, int v, int visited[]) {
    visited[v] = 1;
    for (邻接点 w) {
        if (!visited[w]) dfs(g, w, visited);
    }
}

// BFS模板
void bfs(Graph *g, int start) {
    队列 q;
    visited[start] = 1;
    q.push(start);
    while (q不空) {
        v = q.pop();
        for (邻接点 w) {
            if (!visited[w]) {
                visited[w] = 1;
                q.push(w);
            }
        }
    }
}

下一篇我们讲最小生成树(Prim算法与Kruskal算法)。


九、思考题

  1. 为什么DFS通常用递归实现,而BFS用队列实现?可以互换吗?

  2. 在邻接矩阵表示的图中,DFS和BFS的时间复杂度都是O(V²),哪个常数因子更小?

  3. 如果图是非连通的,如何遍历所有顶点?

  4. 如何用DFS检测图中是否有环?

欢迎在评论区讨论你的答案。

相关推荐
BothSavage5 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn5 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
烬羽6 小时前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
先吃饱再说1 天前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize1 天前
初识DFS 与 BFS:递归、队列与图遍历
算法