图的表示:邻接表和邻接矩阵
本节讲述如何在 C++ 中表示简单图, 本节将介绍邻接矩阵和邻接表的实现. 后续系列的博客中将会以邻接表为主, 偶尔会用到邻接矩阵.
环境要求
本文所用样例在Windows 11
以及Ubuntu 24.04
上面编译通过.
- Windows: 使用[Visual Studio],
- Ubuntu: 使用 Clang 18.1.3. (Ubuntu 24.04 系统安装版本)
- GCC 无法编译直接本项目代码, 因为本文代码使用了 C++20 Module, 而 GCC 对此支持不完整.
概述
本节讨论的图为简单图, 即没有自环边(自己指向自己的边)和平行边(两个顶点之间存在多条边). 根据前一节提到的知识, 我们给出一个简单图的接口:
typedef unsigned Vertex;
typedef int Weight;
typedef std::pair<Vertex, Vertex> Edge;
typedef std::tuple<Vertex, Vertex, Weight> WeightedEdge;
struct Graph {
virtual ~Graph() = default;
// 获取顶点数量
[[nodiscard]] virtual size_t V() const = 0;
// 获取边数量
[[nodiscard]] virtual size_t E() const = 0;
// 该图是有向图吗?
[[nodiscard]] virtual bool Directed() const = 0;
// 该图是带权图吗?
[[nodiscard]] virtual bool Weighted() const = 0;
// 获取顶点u的所有邻接顶点
[[nodiscard]] virtual std::set<Vertex> Adj(Vertex u) const = 0;
// 顶点u,v之间是否存在一条边
[[nodiscard]] virtual bool HasEdge(Vertex u, Vertex v) const = 0;
// 添加边u,v
virtual void AddEdge(Vertex u, Vertex v) = 0;
// 添加带权边u,v
virtual void AddEdge(Vertex u, Vertex v, Weight w) = 0;
// 将图以dot格式输出(Graphviz支持的格式), 可用于可视化
[[nodiscard]] virtual std::string FormatAsDot() const = 0;
};
代码中为AddEdge
增加了两个版本, 分别支持无权图和带权图.
有几点需要说明:
- 顶点和权重的类型均为无符号整型, 这是为了简化编程.
- 查询和操作接口均为纯虚函数, 即子类必须实现这些接口.
- C++虚基类需要将默认析构函数标记为
virtual
, 以保证派生类的析构函数被正确调用.
邻接矩阵
邻接矩阵是 n × n n \times n n×n 矩阵, 元素为两顶点间边数, 简单图邻接矩阵元素为 0 或 1, 主对角线为 0, 空间复杂度 O ( n 2 ) O(n^{2}) O(n2).
在无权图中, 邻接矩阵的元素为 0 或 1, 表示顶点之间是否存在边; 在带权图中, 邻接矩阵的元素为权重, 表示顶点之间边的权重.
在无向图中, 元素 [ m , n ] [m, n] [m,n] 和 [ n , m ] [n, m] [n,m] 相等, 即 m m m 和 n n n 之间存在边; 而在有向图中, [ m , n ] [m, n] [m,