图的两种遍历算法:广度优先搜索(BFS)与深度优先搜索(DFS)

图的两种遍历算法:广度优先搜索(BFS)与深度优先搜索(DFS)

图(Graph)是计算机科学中表示实体间关系的重要数据结构,广泛应用于社交网络、路径规划、网络拓扑分析等领域。图的遍历是图算法的基础,其中 广度优先搜索(BFS)深度优先搜索(DFS) 是两种最核心的遍历方法。本文将从原理、实现、复杂度到经典例题进行系统讲解。


一、图的表示与基本概念

1. 图的定义

图由 顶点(Vertex/Node)边(Edge) 组成,分为:

  • 无向图:边无方向(如朋友关系)。
  • 有向图:边有方向(如网页超链接)。
  • 带权图:边有权重(如地图距离)。

2. 图的存储方式

  • 邻接矩阵:二维数组表示顶点间的连接关系,适合稠密图。
  • 邻接表:链表或数组的数组,存储每个顶点的邻居,适合稀疏图。

二、广度优先搜索(BFS)

1. 算法思想

BFS 按“层次”遍历图,从起点开始逐层向外扩散,确保先访问离起点最近的节点。
核心数据结构:队列(FIFO)。

2. 算法步骤

  1. 初始化队列,将起点加入队列,并标记为已访问。
  2. 循环执行以下操作,直到队列为空:
    • 取出队列头部节点 u
    • 访问 u 的所有未访问邻居节点,依次标记并加入队列。

3. 代码实现(C语言)

#include <stdio.h>
#include <stdlib.h>
#define MAX_NODES 100

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

// 图结构
struct Graph {
    int numVertices;
    struct Node** adjLists;
    int* visited;
};

// 创建节点
struct Node* createNode(int v) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->vertex = v;
    newNode->next = NULL;
    return newNode;
}

// 创建图
struct Graph* createGraph(int vertices) {
    struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
    graph->numVertices = vertices;
    graph->adjLists = (struct Node**)malloc(vertices * sizeof(struct Node*));
    graph->visited = (int*)malloc(vertices * sizeof(int));

    for (int i = 0; i < vertices; i++) {
        graph->adjLists[i] = NULL;
        graph->visited[i] = 0;
    }
    return graph;
}

// 添加边(无向图)
void addEdge(struct Graph* graph, int src, int dest) {
    // 添加 src 到 dest 的边
    struct Node* newNode = createNode(dest);
    newNode->next = graph->adjLists[src];
    graph->adjLists[src] = newNode;

    // 添加 dest 到 src 的边
    newNode = createNode(src);
    newNode->next = graph->adjLists[dest];
    graph->adjLists[dest] = newNode;
}

// BFS 实现
void bfs(struct Graph* graph, int startVertex) {
    int queue[MAX_NODES];
    int front = 0, rear = 0;

    graph->visited[startVertex] = 1;
    queue[rear++] = startVertex;

    while (front < rear) {
        int currentVertex = queue[front++];
        printf("Visited %d\n", currentVertex);

        struct Node* temp = graph->adjLists[currentVertex];
        while (temp) {
            int adjVertex = temp->vertex;
            if (!graph->visited[adjVertex]) {
                graph->visited[adjVertex] = 1;
                queue[rear++] = adjVertex;
            }
            temp = temp->next;
        }
    }
}

4. 复杂度分析

  • 时间复杂度:O(V + E),需访问所有节点和边。
  • 空间复杂度:O(V),队列最多存储一层节点。

5. 典型应用

  • 无权图的最短路径(如迷宫最短步数)。
  • 社交网络中的“六度分隔”理论。
  • 检测图的连通性。

三、深度优先搜索(DFS)

1. 算法思想

DFS 沿一条路径尽可能深入探索,直到无法继续前进时回溯到上一个分叉点。
核心数据结构:栈(LIFO,递归隐式使用系统栈)。

2. 算法步骤

  1. 从起点出发,标记为已访问。
  2. 递归访问当前节点的未访问邻居,直到所有路径都被探索。

3. 代码实现(C语言)

// DFS 递归实现
void dfs(struct Graph* graph, int vertex) {
    struct Node* adjList = graph->adjLists[vertex];
    graph->visited[vertex] = 1;
    printf("Visited %d\n", vertex);

    while (adjList) {
        int connectedVertex = adjList->vertex;
        if (!graph->visited[connectedVertex]) {
            dfs(graph, connectedVertex);
        }
        adjList = adjList->next;
    }
}

4. 复杂度分析

  • 时间复杂度:O(V + E),与 BFS 相同。
  • 空间复杂度:O(V),递归栈深度最坏为节点数。

5. 典型应用

  • 寻找所有可能的路径(如迷宫所有解法)。
  • 拓扑排序(如课程安排依赖)。
  • 检测图中的环。

四、BFS 与 DFS 对比

特性BFSDFS
遍历顺序层次遍历(由近及远)深度优先(一条路走到底)
数据结构队列栈(递归或显式维护)
最短路径天然支持无权图最短路径需要额外记录路径信息
空间复杂度较高(存储一层节点)较低(递归栈深度)
适用场景最短路径、社交网络层级关系全路径搜索、拓扑排序、连通性检测

五、经典例题解析

例题 1:200. 岛屿数量(BFS/DFS)

题目描述
给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿的数量。
解题思路
遍历每个单元格,遇到陆地时启动 BFS/DFS,标记所有相连的陆地,统计启动次数即为岛屿数量。
代码片段(DFS)

void dfs_island(char** grid, int gridSize, int gridColSize, int i, int j) {
    if (i < 0 || i >= gridSize || j < 0 || j >= gridColSize || grid[i][j] != '1') 
        return;
    
    grid[i][j] = '0'; // 标记为已访问
    dfs_island(grid, gridSize, gridColSize, i+1, j);
    dfs_island(grid, gridSize, gridColSize, i-1, j);
    dfs_island(grid, gridSize, gridColSize, i, j+1);
    dfs_island(grid, gridSize, gridColSize, i, j-1);
}

int numIslands(char** grid, int gridSize, int* gridColSize) {
    int count = 0;
    for (int i = 0; i < gridSize; i++) {
        for (int j = 0; j < gridColSize[0]; j++) {
            if (grid[i][j] == '1') {
                dfs_island(grid, gridSize, gridColSize[0], i, j);
                count++;
            }
        }
    }
    return count;
}

例题 2:133. 克隆图(BFS)

题目描述
深度复制一个无向连通图的所有节点。
解题思路
使用 BFS 遍历原图,同时用哈希表存储原节点到克隆节点的映射。
代码片段

struct Node {
    int val;
    int numNeighbors;
    struct Node** neighbors;
};

struct Node* cloneGraph(struct Node* s) {
    if (!s) return NULL;
    
    struct Node* clones[101] = {NULL}; // 假设节点值 <= 100
    struct Node* queue[101];
    int front = 0, rear = 0;
    
    // 创建起始节点克隆
    clones[s->val] = (struct Node*)malloc(sizeof(struct Node));
    clones[s->val]->val = s->val;
    clones[s->val]->numNeighbors = s->numNeighbors;
    clones[s->val]->neighbors = (struct Node**)malloc(s->numNeighbors * sizeof(struct Node*));
    
    queue[rear++] = s;
    
    while (front < rear) {
        struct Node* curr = queue[front++];
        for (int i = 0; i < curr->numNeighbors; i++) {
            struct Node* neighbor = curr->neighbors[i];
            if (!clones[neighbor->val]) {
                clones[neighbor->val] = (struct Node*)malloc(sizeof(struct Node));
                clones[neighbor->val]->val = neighbor->val;
                clones[neighbor->val]->numNeighbors = neighbor->numNeighbors;
                clones[neighbor->val]->neighbors = (struct Node**)malloc(neighbor->numNeighbors * sizeof(struct Node*));
                queue[rear++] = neighbor;
            }
            clones[curr->val]->neighbors[i] = clones[neighbor->val];
        }
    }
    return clones[s->val];
}

例题 3:797. 所有可能路径(DFS)

题目描述
给定有向无环图(DAG),找出从节点 0 到节点 n-1 的所有路径。
解题思路
使用 DFS 回溯,递归记录路径。
代码片段

void dfs_paths(int** graph, int graphSize, int* graphColSize, 
              int* returnSize, int** returnColumnSizes, 
              int** res, int* path, int pathSize, int node) {
    path[pathSize++] = node;
    
    if (node == graphSize - 1) {
        res[*returnSize] = (int*)malloc(pathSize * sizeof(int));
        memcpy(res[*returnSize], path, pathSize * sizeof(int));
        (*returnColumnSizes)[*returnSize] = pathSize;
        (*returnSize)++;
        return;
    }
    
    for (int i = 0; i < graphColSize[node]; i++) {
        dfs_paths(graph, graphSize, graphColSize, returnSize, returnColumnSizes, 
                 res, path, pathSize, graph[node][i]);
    }
}

int** allPathsSourceTarget(int** graph, int graphSize, int* graphColSize, 
                           int* returnSize, int** returnColumnSizes) {
    int** res = (int**)malloc(1000 * sizeof(int*));
    *returnColumnSizes = (int*)malloc(1000 * sizeof(int));
    int* path = (int*)malloc(graphSize * sizeof(int));
    *returnSize = 0;
    
    dfs_paths(graph, graphSize, graphColSize, returnSize, returnColumnSizes, 
             res, path, 0, 0);
    
    free(path);
    return res;
}

六、总结

  • BFS 适用场景:最短路径、层级遍历、最小步数问题。
  • DFS 适用场景:全路径搜索、连通性检测、回溯问题。
  • 关键选择依据:是否需要最短路径,或对空间复杂度有特殊要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值