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

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

相关推荐
吃好睡好便好4 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
仰泳之鹅5 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
2601_957787586 小时前
矩阵运营的技术底座:为什么“一体化系统“正在取代“工具拼装“
人工智能·矩阵·矩阵运营
x_yeyue7 小时前
三角形数
笔记·算法·数论·组合数学
Mr. zhihao8 小时前
深入解析redis基本数据结构
数据结构·数据库·redis
念何架构之路8 小时前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星8 小时前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
失去的青春---夕阳下的奔跑8 小时前
560. 和为 K 的子数组
数据结构·算法·leetcode
黎阳之光9 小时前
黎阳之光:以视频孪生重构智慧医院信息化,打造高标项目核心竞争力
大数据·人工智能·物联网·算法·数字孪生
丷丩9 小时前
三级缓存下MVT地图瓦片服务性能优化策略
算法·缓存·性能优化·gis·geoai-up