图的存储方式(5 种方法详尽解析)
图论基础学习指南
图论是数学中研究图形结构和其性质的分支,它在计算机科学、网络工程等领域有着广泛应用。图是由一组点(顶点)和一组边(连接点的线)组成的集合。图的存储方式多种多样,主要包括邻接矩阵、边集数组、邻接表、链式邻接表以及链式前向星。
以下是各个图论基础存储方式的详细解析。
1. 邻接矩阵
邻接矩阵是一个二维数组,g[u][v]g[u][v]g[u][v] 存储从点 uuu 到点 vvv 的边的权值。对于无向图,矩阵是对称的,即 g[u][v]=g[v][u]g[u][v] = g[v][u]g[u][v]=g[v][u];对于有向图则不一定对称。
时间复杂度:
- 查询 g[u][v]g[u][v]g[u][v] 的时间复杂度为 O(1)O(1)O(1),即常数时间操作。
- 对于邻接矩阵的深度优先搜索(DFS)遍历,时间复杂度为 O(n2)O(n^2)O(n2),其中 nnn 是图的顶点数。
空间复杂度:
- 空间复杂度为 O(n2)O(n^2)O(n2),其中 nnn 是图的顶点数。因为每一对顶点都可能有一条边,所以需要 n2n^2n2 的存储空间。
应用:
- 适用于稠密图(即图中边的数量接近顶点的平方)。例如,在一个图的顶点数 nnn 为 1000,边数 mmm 为 1,000,000 时,邻接矩阵依然有效。
const int N = 1010;
int g[N][N];
int vis[N];
int n, m;
int u, v, w;
void dfs(int u) {
vis[u] = true;
for (int v = 1; v <= n; v++) {
if (g[u][v]) {
if (vis[v]) continue;
dfs(v);
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> u >> v >> w;
g[u][v] = w;
}
dfs(1);
return 0;
}
2. 边集数组
边集数组通过一维数组存储所有的边,每条边包含起点 uuu、终点 vvv 和边权 www。这样,每条边只需要存储一次,因此适合用在边数较少或者边需要排序的场景。
时间复杂度:
- 查询某个顶点的所有出边的时间复杂度为 O(m)O(m)O(m),其中 mmm 是边的数量。
空间复杂度:
- 空间复杂度为 O(m)O(m)O(m),其中 mmm 是边的数量。
应用:
- 在 Kruskal 算法中使用,因其需要对所有边按边权进行排序。也适用于稀疏图,因为边集数组不需要存储空的边。
const int N = 1010, M = 1010;
int n, m, a, b, c;
struct edge {
int u, v, w;
} e[M]; // 边集
int vis[N];
void dfs(int u) {
vis[u] = true;
for (int i = 1; i <= m; i++) {
if (e[i].u == u) {
int v = e[i].v, w = e[i].w;
printf("%d,%d,%d\n", u, v, w);
if (vis[v]) continue;
dfs(e[i].v);
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
e[i] = {a, b, c};
}
dfs(1);
return 0;
}
3. 邻接表
邻接表使用一维数组 e[u]e[u]e[u] 存储每个顶点 uuu 的所有出边,每条边由终点 vvv 和边权 www 表示。每个点的边可以存储在一个链表或者数组中。邻接表相比于邻接矩阵节省了存储空间,特别适合存储稀疏图。
时间复杂度:
- 查询 uuu 的所有出边的时间复杂度为 O(du)O(d_u)O(du),其中 dud_udu 是顶点 uuu 的出度。
- 深度优先搜索(DFS)的时间复杂度为 O(n+m)O(n + m)O(n+m),其中 nnn 是顶点数,mmm 是边数。
空间复杂度:
- 空间复杂度为 O(n+m)O(n + m)O(n+m),其中 nnn 是顶点数,mmm 是边数。
应用:
- 各种图的表示,特别适合稀疏图。但不能处理反向边,需单独存储。
const int N = 510;
int n, m, a, b, c;
struct edge { int v, w; };
vector<edge> e[N]; // 边集
void dfs(int u, int fa) {
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (v == fa) continue;
printf("%d,%d,%d\n", u, v, w);
dfs(v, u);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
e[a].push_back({b, c});
}
dfs(1, 0);
return 0;
}
4. 链式邻接表
链式邻接表采用结构体形式,将边与相邻节点的索引信息结合。每条边 eee 不仅保存终点 vvv 和边权 www,还保存下一条边的索引 nenene。链式结构通过指向邻接的边来进行存储。
时间复杂度:
- 查询某个顶点 uuu 的所有出边的时间复杂度为 O(du)O(d_u)O(du),其中 dud_udu 是顶点 uuu 的出度。
空间复杂度:
- 空间复杂度为 O(n+m)O(n + m)O(n+m),其中 nnn 是顶点数,mmm 是边数。
应用:
- 常用于图的遍历,且能够更有效地进行修改操作,适合有大量边的稀疏图。
const int N = 510;
int n, m, a, b, c;
struct edge { int u, v, w; };
vector<edge> e; // 边集
vector<int> h[N]; // 点的所有出边
void add(int a, int b, int c) {
e.push_back({a, b, c});
h[a].push_back(e.size() - 1);
}
void dfs(int u, int fa) {
for (int i = 0; i < h[u].size(); i++) {
int j = h[u][i];
int v = e[j].v, w = e[j].w;
if (v == fa) continue;
printf("%d,%d,%d\n", u, v, w);
dfs(v, u);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dfs(1, 0);
return 0;
}
5. 链式前向星
链式前向星是链式邻接表的变种,它通过异或运算处理边的反向边。对于一条边的下标 iii,其反向边的下标为 i⊕1i \oplus 1i⊕1,这种特性在网络流中非常有用。
时间复杂度:
- 查询某个顶点的所有出边的时间复杂度为 O(du)O(d_u)O(du),其中 dud_udu 是顶点 uuu 的出度。
空间复杂度:
- 空间复杂度为 O(n+m)O(n + m)O(n+m),其中 nnn 是顶点数,mmm 是边数。
应用:
- 适用于网络流等需要反向边操作的图问题。
const int N = 510, M = 3000;
int n, m, a, b, c;
struct edge { int v, w, ne; };
edge e[M]; // 边集
int idx, h[N]; // 点的第一条出边
void add(int a, int b, int c) {
e[idx] = {b, c, h[a]};
h[a] = idx++;
}
void dfs(int u, int fa) {
for (int i = h[u]; ~i; i = e[i].ne) {
int v = e[i].v, w = e[i].w;
if (v == fa) continue;
printf("%d,%d,%d\n", u, v, w);
dfs(v, u);
}
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dfs(1, 0);
return 0;
}
总结
- 邻接矩阵:适用于稠密图,空间复杂度高,但查询和更新边的操作非常快速。
- 边集数组:适用于边比较少的图,特别是在 Kruskal 算法中。
- 邻接表:适用于大多数图,节省空间,适合稀疏图。
- 链式邻接表:更灵活,能高效处理修改操作,适合稀疏图。
- 链式前向星:专门用于网络流等问题,能够高效处理反向边。
不同的存储方式适用于不同的应用场景,具体选择依据图的稠密程度和操作需求。

浙公网安备 33010602011771号