图的存储方式(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 1i1,这种特性在网络流中非常有用。

时间复杂度:
  • 查询某个顶点的所有出边的时间复杂度为 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 算法中。
  • 邻接表:适用于大多数图,节省空间,适合稀疏图。
  • 链式邻接表:更灵活,能高效处理修改操作,适合稀疏图。
  • 链式前向星:专门用于网络流等问题,能够高效处理反向边。

不同的存储方式适用于不同的应用场景,具体选择依据图的稠密程度和操作需求。

posted @ 2025-07-31 23:55  晓律  阅读(60)  评论(0)    收藏  举报  来源