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

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

相关推荐
一只侯子17 小时前
Face AE Tuning
图像处理·笔记·学习·算法·计算机视觉
jianqiang.xue17 小时前
别把 Scratch 当 “动画玩具”!图形化编程是算法思维的最佳启蒙
人工智能·算法·青少年编程·机器人·少儿编程
不许哈哈哈18 小时前
Python数据结构
数据结构·算法·排序算法
J***793918 小时前
后端在分布式系统中的数据分片
算法·哈希算法
sin_hielo20 小时前
leetcode 2872
数据结构·算法·leetcode
dragoooon3420 小时前
[优选算法专题八.分治-归并 ——NO.49 翻转对]
算法
AI科技星20 小时前
为什么宇宙无限大?
开发语言·数据结构·经验分享·线性代数·算法
Zero-Talent21 小时前
位运算算法
算法
不穿格子的程序员21 小时前
从零开始刷算法——双指针-三数之和&接雨水
算法·双指针
无限进步_1 天前
C语言数组元素删除算法详解:从基础实现到性能优化
c语言·开发语言·windows·git·算法·github·visual studio