图的遍历:从深度优先到广度优先

图是计算机科学中非常重要的数据结构,广泛应用于许多问题中,如社交网络分析、地图导航、路径查找、网络路由等。而图遍历是图的基本操作之一,用于系统地访问图中的每个节点。图的遍历方式主要有两种:深度优先搜索(DFS)广度优先搜索(BFS)。这篇博客将介绍这两种图的遍历算法,并给出其实现方法和应用场景。

什么是图遍历?

图遍历(Graph Traversal)指的是访问图中所有节点(或边)的一种方法。图的遍历操作不仅需要访问每个节点,还需要确保图中的每一条边也被合适地访问过。根据遍历的顺序和方法,常见的遍历策略有两种:深度优先搜索(DFS)广度优先搜索(BFS)

图的基本表示

在谈到图的遍历之前,我们先简要回顾一下图的常见表示方法。常见的图表示有两种:

  1. 邻接矩阵(Adjacency Matrix) :用一个二维数组 adj[i][j] 来表示从节点 i 到节点 j 是否有边。适用于边数较多的图。

  2. 邻接表(Adjacency List):用一个链表或向量来存储每个节点的邻接节点。适用于稀疏图。

深度优先搜索(DFS)

定义

深度优先搜索(DFS)是一种优先遍历图的子节点的遍历方式。它的基本思想是:从一个起始节点出发,尽可能深入地访问未访问的节点,直到没有更多未访问的邻接节点为止,然后回溯到上一个节点,继续尝试访问其他邻接节点。

步骤

  1. 从起始节点开始访问,标记为已访问。

  2. 递归地访问该节点的所有未访问的邻接节点。

  3. 如果一个节点的所有邻接节点都已访问,回溯到父节点。

实现代码(C++)

复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<vector<int>> graph;
vector<bool> visited;

void dfs(int node) {
    visited[node] = true;  // 标记节点为已访问
    cout << node << " ";    // 访问当前节点
    
    // 遍历所有邻接节点
    for (int neighbor : graph[node]) {
        if (!visited[neighbor]) {
            dfs(neighbor);  // 递归访问未访问的邻接节点
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;  // n 为节点数,m 为边数
    
    graph.resize(n + 1);  // 初始化图
    visited.resize(n + 1, false);  // 初始化访问标记
    
    // 输入图的边
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);  // 无向图
    }
    
    // 从节点 1 开始进行 DFS 遍历
    dfs(1);
    
    return 0;
}

特点

  • 空间复杂度O(V),其中 V 是图的节点数。递归调用栈的最大深度为 V

  • 时间复杂度O(V + E),其中 E 是图的边数。每个节点和边会被访问一次。

应用场景

  • 路径查找:在某些应用中,如迷宫求解,可以使用 DFS 找到一条路径。

  • 图的连通性:DFS 可以用来判断图是否是连通的,或者求图中的连通分量。

  • 拓扑排序:在有向无环图(DAG)中,DFS 是实现拓扑排序的常用方法。

广度优先搜索(BFS)

定义

广度优先搜索(BFS)是一种层次遍历的策略,它从起始节点开始,首先访问所有与起始节点直接相连的节点,再访问这些节点的邻接节点,以此类推,直到遍历整个图。BFS 使用队列来实现。

步骤

  1. 从起始节点开始访问,标记为已访问。

  2. 将该节点的所有未访问的邻接节点加入队列。

  3. 从队列中取出一个节点,访问它的邻接节点,并将它们加入队列。

  4. 重复步骤3,直到队列为空。

实现代码(C++)

复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

vector<vector<int>> graph;
vector<bool> visited;

void bfs(int start) {
    queue<int> q;
    visited[start] = true;
    q.push(start);
    
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        cout << node << " ";  // 访问当前节点
        
        // 将未访问的邻接节点加入队列
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                q.push(neighbor);
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;  // n 为节点数,m 为边数
    
    graph.resize(n + 1);  // 初始化图
    visited.resize(n + 1, false);  // 初始化访问标记
    
    // 输入图的边
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);  // 无向图
    }
    
    // 从节点 1 开始进行 BFS 遍历
    bfs(1);
    
    return 0;
}

特点

  • 空间复杂度O(V),队列存储了最坏情况下所有节点。

  • 时间复杂度O(V + E),每个节点和边都会被访问一次。

应用场景

  • 最短路径问题:在无权图中,BFS 用来寻找从起始节点到目标节点的最短路径。

  • 层次遍历:BFS 能够按层次遍历图,适合用于层次分析。

  • 图的广度分析:BFS 可用于分析图的所有邻接关系。

DFS 与 BFS 的比较

特性 DFS BFS
遍历方式 深入到子节点再回溯 按层次逐层访问
使用的数据结构 栈(递归调用栈或显式栈) 队列
时间复杂度 O(V + E) O(V + E)
空间复杂度 O(V) O(V)
应用场景 路径查找、拓扑排序、连通性 最短路径、层次遍历

总结

图的遍历是图论中的基本操作,DFS 和 BFS 各有特点。DFS 更适合用于需要深入探索子节点的场景,而 BFS 更适合层次遍历或寻找最短路径的场景。理解这两种遍历方法及其应用场景,能够帮助我们更好地解决图相关的问题。

希望这篇博客能够帮助你理解图的遍历方法,掌握 DFS 和 BFS 的实现,并能够在实际问题中灵活应用。

相关推荐
明月_清风21 分钟前
多模式字符串匹配:Trie 与 AC 自动机
后端·算法
木井巳27 分钟前
【BFS/DFS 解决 FloodFill 算法】衣橱整理
java·算法·leetcode·深度优先·广度优先·宽度优先
热心网友俣先生32 分钟前
2026年高教社杯数模国赛A题:五年命题规律与预测
算法·机器学习·数学建模
LuminousCPP1 小时前
数据结构 - 排序(二):快速排序从错误初版到优化版|双指针划分 + 三数取中 + 小区间插入优化
c语言·数据结构·笔记·算法·排序算法
hansang_IR2 小时前
【题解】P9753 [CSP-S 2023] 消消乐
数据结构·c++·算法
shehuiyuelaiyuehao2 小时前
算法40,模拟运算,替换所有的问号
数据结构·算法·leetcode
用户204937554952 小时前
从“能识别”到“稳定识别”:离线ASR在真实会议场景中的问题与工程优化实践
算法
鹿角片ljp2 小时前
LeetCode 148:排序链表|归并排序、快慢指针找左中点与链表断开
算法
LabVIEW开发2 小时前
LabVIEW字符串特殊字符检测兼容
算法·labview·labview知识·labview功能·labview程序
晴天的雨.9923 小时前
[C++算法]快乐数
数据结构·c++·算法