【数据结构与算法】第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检测图中是否有环?

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

相关推荐
小欣加油7 小时前
leetcode56 合并区间
c++·算法·leetcode·职场和发展
lqqjuly7 小时前
前沿算法深度解析(二)
人工智能·算法·机器学习
徐小夕8 小时前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
akunkuntaimei8 小时前
2026年高考数学各省真题及答案(完整版)
算法·高考
Hello:CodeWorld9 小时前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法
8Qi810 小时前
LeetCode 516:最长回文子序列
算法·leetcode·职场和发展·动态规划
youngerwang11 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
想要成为糕糕手12 小时前
前端必修课:JavaScript 数组与数据结构底层逻辑全解析
javascript·数据结构·面试
KaMeidebaby12 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
dingzd9512 小时前
跨境社媒运营越到后面 越比拼账号的表达稳定性
大数据·人工智能·矩阵·内容营销