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

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

相关推荐
GuWenyue1 小时前
传统Agent工具两大痛点!300行代码落地MCP跨语言工具,彻底解耦LLM与工具
前端·人工智能·算法
我叫洋洋3 小时前
C ++ [ hello world ]
c语言·c++·算法
奋发向前wcx4 小时前
y1,y2总复习笔记5 2026.7.19
数据结构·笔记·算法
战族狼魂7 小时前
高频面试题精选:分治与AI Agent架构
人工智能·算法·大模型·大语言模型
李剑一7 小时前
你用过网易的将军令嘛?它底层实现账号保护的原理相当简单粗暴
算法
Scott9999HH7 小时前
告别流量波动玄学!从法拉第电磁定律到底层 C/C++ 流量累积算法,深度解密工业级电磁流量计选型与开发
c语言·c++·算法
中达瑞和-高光谱·多光谱8 小时前
1nm到8nm光谱分辨率,你的应用该选哪款光谱相机?
算法·高光谱·高光谱相机
ChaoZiLL8 小时前
我的数据结构4-栈和队列
数据结构
香辣牛肉饭8 小时前
【算法】动态规划 最长公共子序列(LCS)
经验分享·笔记·算法·动态规划
miller-tsunami9 小时前
顺序表相关知识点
数据结构·顺序表