图的存储
图的常见存储方式有三种:邻接表、邻接矩阵、链式向前星。
1. 邻接矩阵
使用二维数组来存储图,二维数组下标代表点,二维数组的值代表连边。
更具体地,我们一般使用 bool
数组来储存点与点之间的连边的信息:
-
若
adj[i][j] = true
,表示点 i i i 和 点 j j j 之间存在一条由 i i i 指向 j j j 的边; -
若
adj[i][j] = false
,表示点 i i i 和点 j j j 之间没有连边。
优点:
矩阵存图法各种操作的时间复杂度相对来说都比较为优秀;
比如:想要添加一条从
i
i
i 连向
j
j
j 的边,我们只需要将 adj[i][j]
的值设置为 true
即可。
缺点:
邻接矩阵只适用于没有重边(或重边可以忽略)的情况。
存下一个图需要 O ( n 2 ) O(n^{2}) O(n2) 的空间复杂度,如果是点与点之间的连边数远小于 O ( n 2 ) O(n^{2}) O(n2) 的稀疏图,就有很多空间被浪费。
对于加权图来说,把
bool
数组改为int
数组即可;
对于无向图来说,我们只需要同时存两条有向边,即令adj[i][j] = adj[j][i] = true/false
。
2. 邻接表
使用一个动态数组,如 vector<vector<int> > adjacencyList(n)
来存边,其中 adjacencyList[u]
存储的是点
u
u
u 的所有出边的相关信息(终点、权重等)。
-
对于无权重的有向图来说,
adjacencyList[u]
只需要记录点 u 的所有出边上的终点即可; -
对于加权有向图来说,可以创建一个结构体
Edge
来记录终点和权重。
// 边的数据结构
struct Edge {
int destination;
int weight; // 如果需要权重的话
};
// 图的邻接表表示
class Graph {
public:
Graph(int vertices);
void addEdge(int source, int destination, int weight = 1);
private:
int vertices; // 端点的数目
vector<vector<Edge>> adjacencyList; // 二维数组存储边
};
// 构造函数,初始化邻接表
Graph::Graph(int vertices) {
this->vertices = vertices;
adjacencyList.resize(vertices);
}
// 添加边到邻接表
void Graph::addEdge(int source, int destination, int weight) {
Edge edge;
edge.destination = destination;
edge.weight = weight;
adjacencyList[source].push_back(edge);
// 如果是无向图,还需要添加反向边
// Edge reverse_edge;
// reverse_edge.destination = source;
// reverse_edge.weight = weight;
// adjacencyList[destination].push_back(reverse_edge);
}
对于邻接表的存储方法来说,有无重边是不影响的!
那么,假设我们想要使用上述方法来存储一个图,会有什么特点呢?
可以想到的一点是,每个端点的编号是默认从 0 开始的连续整数;如果我们想要让每个端点拥有它自己的专属且独特的编号,可以用哈希表来实现;例如,使用unordered_map<int, vector<Edge> > adjacencyList(n)
来存储图。
相应地,需要修改以下部分:
// 图的邻接表表示
class Graph {
public:
// 不需要原先的构造函数,因为 unordered_map 没有 resize()函数
void addEdge(int source, int destination, int weight);
private:
unordered_map<int, vector<Edge>> adjacencyList;
};
3. 链式向前星
链式向前星的思想是:使用链表来存储每个顶点的邻接边,以便有效地表示稀疏图。
// 链式向前星的节点结构体,包括节点编号和指向下一条边的指针
struct ListNode {
int vertex;
int weight;
ListNode* next;
ListNode(int _vertex, int _weight) : vertex(_vertex), weight(_weight), next(nullptr) {}
};
// 图的类定义
class Graph {
public:
int vertices; // 图中的顶点数量
vector<ListNode*> adjList; // 链式向前星数组
Graph(int V) : vertices(V), adjList(V, nullptr) {}
// 添加一条有向边
void addEdge(int from, int dest, int weight) {
ListNode* node = new ListNode(dest, weight);
node -> next = adjList[from];
adjList[from] = node;
}
// 打印链式向前星
void print() {
for (int i = 0; i < vertices; ++i) {
cout << "Node " << i << " is connected to: ";
ListNode* node = adjList[i];
while (node != nullptr) {
cout << "(" << node -> vertex << ", Weight: " << node -> weight << ") ";
node = node -> next;
}
cout << endl;
}
}
};
同理,如果想要让每个端点拥有它自己的专属且独特的编号,可以用哈希表来实现,这里略过,参考邻接表的实现。