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

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

相关推荐
佑白雪乐2 小时前
<LeetCode>二叉树前/中/后/层遍历**递归&&非递归**
算法·leetcode·深度优先
汀、人工智能2 小时前
[特殊字符] 第56课:在排序数组中查找元素的首末位置
数据结构·算法·数据库架构·图论·bfs·在排序数组中查找元素的首末位置
小O的算法实验室2 小时前
2026年IEEE TASE,面对突发危险区域的基于强化学习的多无人机路径规划,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进
AI科技星2 小时前
全维度相对论推导、光速螺旋时空与北斗 GEO 钟差的统一理论
开发语言·线性代数·算法·机器学习·数学建模
ECT-OS-JiuHuaShan2 小时前
科学的本来意义,是基于规范的共识逻辑,而非共识方法
人工智能·科技·学习·算法·生活
木子墨5162 小时前
LeetCode 热题 100 精讲 | 动态规划进阶篇:最大子数组和 · 分割等和子集 · 最长公共子序列 · 打家劫舍 III
数据结构·c++·算法·leetcode·动态规划·力扣
li1670902702 小时前
第十章:list
c语言·开发语言·数据结构·c++·算法·list·visual studio
Z1Jxxx2 小时前
C++ P1150 Peter 的烟
数据结构·c++·算法
踮起脚看烟花2 小时前
chapter10_泛型算法
c++·算法