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

图是计算机科学中非常重要的数据结构,广泛应用于许多问题中,如社交网络分析、地图导航、路径查找、网络路由等。而图遍历是图的基本操作之一,用于系统地访问图中的每个节点。图的遍历方式主要有两种:深度优先搜索(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 的实现,并能够在实际问题中灵活应用。

相关推荐
高山有多高3 小时前
C语言实战项目:贪吃蛇(1)
c语言·开发语言·数据结构·c++·算法·游戏
微笑尅乐3 小时前
多种解法全解析——力扣217. 存在重复元素
算法·leetcode·职场和发展
Jiezcode3 小时前
LeetCode 199.二叉树的右视图
c++·算法·leetcode·深度优先
10001hours3 小时前
C语言第23讲
c语言·开发语言·算法
MoRanzhi12033 小时前
基于 SciPy 的矩阵运算与线性代数应用详解
人工智能·python·线性代数·算法·数学建模·矩阵·scipy
仰泳的熊猫3 小时前
LeetCode:239. 滑动窗口最大值
数据结构·c++·算法·leetcode
CoovallyAIHub3 小时前
版本号突袭!官方预览:YOLO26正式宣布,10月发布,CPU推理速度提升43%
深度学习·算法·计算机视觉
Ms.lan4 小时前
C++数组
数据结构·c++·算法·visual studio
~~李木子~~4 小时前
归并排序算法
数据结构·算法·排序算法