news 2026/7/13 21:55:13

用邻接表和邻接矩阵实现图结构:C++实战代码详解(附DFS/BFS对比)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用邻接表和邻接矩阵实现图结构:C++实战代码详解(附DFS/BFS对比)

用邻接表和邻接矩阵实现图结构:C++实战代码详解(附DFS/BFS对比)

在算法与数据结构的世界里,图(Graph)无疑是最具表现力的结构之一。无论是社交网络中的好友关系,还是城市间的交通路线,甚至是编译器中的依赖分析,图都能完美建模这些复杂关系。作为C++开发者,掌握图的两种经典实现方式——邻接表和邻接矩阵,以及它们的核心遍历算法DFS和BFS,是处理复杂关系数据的必备技能。

本文将深入探讨这两种实现方式的代码细节,分析它们在不同场景下的性能表现,并通过完整的C++示例展示如何在实际项目中应用这些知识。我们不仅会实现基础结构,还会对比DFS和BFS在路径查找、环检测等实际场景中的差异,帮助你在算法竞赛和工程开发中做出更明智的选择。

1. 图的两种实现方式:邻接表与邻接矩阵

1.1 邻接表:灵活的内存使用

邻接表是图最常用的存储方式之一,特别适合稀疏图(边数远小于顶点数平方的图)。它的核心思想是为每个顶点维护一个链表,存储与之直接相连的顶点。

// 使用vector和list实现邻接表 #include <vector> #include <list> class Graph { private: int V; // 顶点数 std::vector<std::list<int>> adj; // 邻接表 public: Graph(int vertices) : V(vertices), adj(vertices) {} void addEdge(int src, int dest) { adj[src].push_back(dest); // 如果是无向图,需要添加反向边 // adj[dest].push_back(src); } const std::list<int>& getNeighbors(int vertex) const { return adj[vertex]; } };

邻接表的核心优势:

  • 空间效率高:只存储实际存在的边,空间复杂度为O(V+E)
  • 添加边快速:O(1)时间复杂度
  • 遍历某顶点的邻接点高效:O(degree(V))时间复杂度

提示:在C++中,使用vector代替list通常能获得更好的缓存局部性,除非频繁在中间插入/删除元素。

1.2 邻接矩阵:快速的查询性能

邻接矩阵使用二维数组表示图,矩阵的行和列分别代表顶点,矩阵值表示边是否存在(或权重)。

// 邻接矩阵实现 #include <vector> class GraphMatrix { private: int V; std::vector<std::vector<int>> matrix; public: GraphMatrix(int vertices) : V(vertices), matrix(vertices, std::vector<int>(vertices, 0)) {} void addEdge(int src, int dest, int weight = 1) { matrix[src][dest] = weight; // 无向图需要对称设置 // matrix[dest][src] = weight; } bool hasEdge(int src, int dest) const { return matrix[src][dest] != 0; } int getWeight(int src, int dest) const { return matrix[src][dest]; } };

邻接矩阵的典型特征:

  • 查询边是否存在:O(1)时间复杂度
  • 空间复杂度:O(V²),适合稠密图
  • 添加/删除边:O(1)时间复杂度
  • 遍历所有邻接点:O(V)时间复杂度,与边数无关

1.3 实现方式对比与选择指南

特性邻接表邻接矩阵
空间复杂度O(V + E)O(V²)
添加边O(1)O(1)
删除边O(degree(V))O(1)
检查边是否存在O(degree(V))O(1)
遍历邻接点O(degree(V))O(V)
适用场景稀疏图、边遍历频繁的应用稠密图、频繁查询边的应用

在实际项目中,选择哪种实现取决于具体需求:

  • 社交网络:通常选择邻接表,因为用户好友关系是典型的稀疏图
  • 交通网络:城市间直飞航线较少,邻接表更合适
  • 图像处理:像素邻接关系密集,邻接矩阵可能更好

2. 深度优先搜索(DFS)实现与优化

2.1 递归实现:简洁直观

DFS遵循"一条路走到黑"的策略,尽可能深地探索图的分支。

void DFS_recursive(const Graph& g, int v, std::vector<bool>& visited) { visited[v] = true; std::cout << v << " "; for (int neighbor : g.getNeighbors(v)) { if (!visited[neighbor]) { DFS_recursive(g, neighbor, visited); } } }

递归DFS的特点:

  • 代码简洁,直接反映算法逻辑
  • 隐式使用调用栈
  • 可能面临栈溢出风险(深度超过几千层)

2.2 迭代实现:避免栈溢出

使用显式栈的迭代实现能避免递归深度限制。

#include <stack> void DFS_iterative(const Graph& g, int start) { std::vector<bool> visited(g.vertexCount(), false); std::stack<int> s; s.push(start); while (!s.empty()) { int v = s.top(); s.pop(); if (!visited[v]) { visited[v] = true; std::cout << v << " "; // 注意:邻接点入栈顺序会影响遍历顺序 for (auto it = g.getNeighbors(v).rbegin(); it != g.getNeighbors(v).rend(); ++it) { if (!visited[*it]) { s.push(*it); } } } } }

注意:迭代实现中邻接点入栈顺序与递归版不同,会导致遍历顺序差异,但不影响DFS性质。

2.3 DFS应用场景

  1. 拓扑排序:有向无环图的线性排序

    void topologicalSortUtil(int v, std::vector<bool>& visited, std::stack<int>& stack, const Graph& g) { visited[v] = true; for (int neighbor : g.getNeighbors(v)) { if (!visited[neighbor]) { topologicalSortUtil(neighbor, visited, stack, g); } } stack.push(v); }
  2. 连通分量检测:查找图中所有连通区域

    void findConnectedComponents(const Graph& g) { std::vector<bool> visited(g.vertexCount(), false); for (int v = 0; v < g.vertexCount(); ++v) { if (!visited[v]) { DFS_recursive(g, v, visited); std::cout << "\n--- New Component ---\n"; } } }
  3. 环路检测:判断有向图是否包含环

    bool isCyclicUtil(int v, std::vector<bool>& visited, std::vector<bool>& recStack, const Graph& g) { if (!visited[v]) { visited[v] = true; recStack[v] = true; for (int neighbor : g.getNeighbors(v)) { if (!visited[neighbor] && isCyclicUtil(neighbor, visited, recStack, g)) { return true; } else if (recStack[neighbor]) { return true; } } } recStack[v] = false; return false; }

3. 广度优先搜索(BFS)实现与优化

3.1 标准BFS实现

BFS使用队列实现"层层推进"的遍历策略,天然适合寻找最短路径。

#include <queue> void BFS(const Graph& g, int start) { std::vector<bool> visited(g.vertexCount(), false); std::queue<int> q; q.push(start); visited[start] = true; while (!q.empty()) { int v = q.front(); q.pop(); std::cout << v << " "; for (int neighbor : g.getNeighbors(v)) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } } }

BFS的关键特性:

  • 使用队列保证先进先出的访问顺序
  • 非递归实现,无栈溢出风险
  • 首次访问时即找到最短路径(无权图)

3.2 BFS应用场景

  1. 最短路径查找(无权图)

    std::vector<int> shortestPaths(const Graph& g, int start) { std::vector<int> distances(g.vertexCount(), -1); std::queue<int> q; q.push(start); distances[start] = 0; while (!q.empty()) { int v = q.front(); q.pop(); for (int neighbor : g.getNeighbors(v)) { if (distances[neighbor] == -1) { distances[neighbor] = distances[v] + 1; q.push(neighbor); } } } return distances; }
  2. 网络爬虫层级抓取:按距离分批抓取网页

    void crawlWeb(const Graph& webGraph, int startPage) { std::vector<bool> visited(webGraph.vertexCount(), false); std::queue<std::pair<int, int>> q; // <page, level> q.push({startPage, 0}); visited[startPage] = true; while (!q.empty()) { auto current = q.front(); q.pop(); std::cout << "Crawling page " << current.first << " at level " << current.second << "\n"; if (current.second < 3) { // 限制抓取深度 for (int neighbor : webGraph.getNeighbors(current.first)) { if (!visited[neighbor]) { visited[neighbor] = true; q.push({neighbor, current.second + 1}); } } } } }
  3. 社交网络好友推荐:寻找二度、三度好友

    void recommendFriends(const Graph& socialGraph, int user, int maxDegree) { std::vector<bool> visited(socialGraph.vertexCount(), false); std::queue<std::pair<int, int>> q; // <user, degree> q.push({user, 0}); visited[user] = true; while (!q.empty()) { auto current = q.front(); q.pop(); if (current.second > 0 && current.second <= maxDegree) { std::cout << "Recommended friend: " << current.first << " (degree " << current.second << ")\n"; } if (current.second < maxDegree) { for (int neighbor : socialGraph.getNeighbors(current.first)) { if (!visited[neighbor]) { visited[neighbor] = true; q.push({neighbor, current.second + 1}); } } } } }

4. DFS与BFS的对比与实战选择

4.1 算法特性对比

特性DFSBFS
数据结构栈(显式或隐式)队列
空间复杂度O(h)(h为最大深度)O(w)(w为最大宽度)
最短路径不保证(无权图)保证(无权图)
适用场景拓扑排序、连通分量、环路最短路径、层级遍历
实现复杂度递归简单,迭代稍复杂统一使用队列较简单
内存消耗通常较少(除非图很深)可能较大(图很宽时)

4.2 实际应用选择指南

  1. 路径查找

    • 需要最短路径 → BFS
    • 需要所有可能路径 → DFS
    • 带权图 → Dijkstra或A*算法
  2. 图结构分析

    • 检测环路 → DFS
    • 连通分量 → DFS或BFS
    • 拓扑排序 → DFS
  3. 性能考量

    • 图又宽又浅 → BFS可能更快
    • 图又窄又深 → DFS可能更省内存
    • 已知目标顶点距离近 → BFS
    • 目标可能在深层 → DFS

4.3 性能优化技巧

  1. 双向BFS:当起点和终点都已知时,从两端同时进行BFS,相遇时即找到路径。

    int bidirectionalBFS(const Graph& g, int start, int target) { std::vector<bool> visitedFromStart(g.vertexCount(), false); std::vector<bool> visitedFromTarget(g.vertexCount(), false); std::queue<int> qStart, qTarget; qStart.push(start); visitedFromStart[start] = true; qTarget.push(target); visitedFromTarget[target] = true; while (!qStart.empty() && !qTarget.empty()) { // 从起点端扩展 int size = qStart.size(); for (int i = 0; i < size; ++i) { int v = qStart.front(); qStart.pop(); if (visitedFromTarget[v]) { return ...; // 计算合并路径长度 } for (int neighbor : g.getNeighbors(v)) { if (!visitedFromStart[neighbor]) { visitedFromStart[neighbor] = true; qStart.push(neighbor); } } } // 从终点端扩展(类似代码) // ... } return -1; // 无连接 }
  2. 迭代加深DFS:结合DFS空间效率和BFS完备性的混合算法。

    bool DLS(const Graph& g, int v, int target, int limit) { if (v == target) return true; if (limit <= 0) return false; for (int neighbor : g.getNeighbors(v)) { if (DLS(g, neighbor, target, limit - 1)) { return true; } } return false; } bool IDDFS(const Graph& g, int start, int target, int max_depth) { for (int depth = 0; depth <= max_depth; ++depth) { if (DLS(g, start, target, depth)) { return true; } } return false; }
  3. 并行化搜索:对于大型图,可以考虑并行化DFS或BFS。

    • 对BFS,可以并行处理同一层的多个节点
    • 对DFS,可以并行探索不同的分支

在真实项目中使用这些算法时,我通常会根据图的大小和特性先进行小规模测试。比如在社交网络分析中,当需要查找用户间的潜在联系时,BFS通常是首选;而在处理依赖关系解析时,DFS的拓扑排序能力则更为关键。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/23 2:23:47

G-Helper:华硕笔记本的终极轻量级性能控制工具完全指南

G-Helper&#xff1a;华硕笔记本的终极轻量级性能控制工具完全指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops. Control tool for ROG Zephyrus G14, G15, G16, M16, Flow X13, Flow X16, TUF, Strix, Scar and other models 项目地…

作者头像 李华
网站建设 2026/3/23 2:22:23

AutoCAD Plant 3D 管道建模太慢?我用Python脚本5分钟搞定一个定制三通

AutoCAD Plant 3D管道建模效率革命&#xff1a;Python脚本实现非标三通5分钟参数化设计 在工厂管道设计领域&#xff0c;时间就是金钱。上周五下午4点30分&#xff0c;某化工项目团队正面临一个紧急挑战&#xff1a;需要在1小时内完成一套非标尺寸槽式垂直三通的模型修改并提交…

作者头像 李华
网站建设 2026/3/23 2:21:27

基于 YOLOv11 的蘑菇品种检测系统

基于 YOLOv11 的蘑菇品种检测系统 项目概述 本项目是一个基于深度学习技术的蘑菇品种智能检测系统&#xff0c;采用最新的YOLOv11目标检测算法&#xff0c;能够准确识别21种常见蘑菇品种。系统集成了完整的数据处理、模型训练、性能评估和Web应用界面&#xff0c;为蘑菇品种识别…

作者头像 李华
网站建设 2026/3/23 2:20:27

FastSurfer完整指南:如何在5分钟内完成大脑MRI分割的深度学习工具

FastSurfer完整指南&#xff1a;如何在5分钟内完成大脑MRI分割的深度学习工具 【免费下载链接】FastSurfer 项目地址: https://gitcode.com/gh_mirrors/fa/FastSurfer 在神经影像分析领域&#xff0c;传统的大脑MRI分割工具往往需要数小时甚至数天才能完成处理。而Fast…

作者头像 李华