最小生成树(MST)

本文同步发于知乎

定义

原图:\(G=(V,E)\)

生成子图:\(G'=(V,E')\)

树:\(N\) 个点,\(N-1\) 条边的无向连通图

最小生成树:(生成子图+树)边权和最小

Prim(稠密图)

算法

  1. 初始边集 \(E=\emptyset\),点集 \(V=\{任意一个点\}\)
  2. 选择一条权值 \(w\) 最小的边 \(e=(u,v)\),满足 \(u\in V, v\notin V\)
  3. \(E=E+{e},V=V+{v}\)
  4. 点集 \(V\) 包含所有节点则算法结束,否则返回第二步

时间复杂度

\(\Theta(V^2)\)

使用 优先队列 加速 过程 2,时间复杂度: \(\Theta(V\log V+E\log V)=\Theta(E\log V)\)
使用 斐波那契堆,可进一步优化到 \(\Theta(E+V\log V)\)

证明

  • 令 Prim 算法得到的树为 \(P\),有一棵最小生成树 \(T\),假设他们不同
  • 假设前 \(k-1\)\(P\) 选择的边都在 \(T\) 中,令此时的树为 \(P'\)
  • \(k\) 步选择的 \(e=(u,v)\) 不在 \(T\) 中,假设 \(u\)\(P'\) 中,而 \(v\) 不在
  • \(T\) 中必有一条 \(u\rightarrow v\) 的路径,路径上必有一条边 \(e'=(x,y)\) 满足此时 \(x\)\(P'\) 中而 \(y\) 不在
  • \(w(e')>w(e)\) 则在 \(T\) 中用 \(e\) 换掉 \(e'\) 可得到一个更小的生成树,矛盾
  • \(w(e')<w(e)\) 则第 \(k\) 步时选的是 \(e'\) 而不是 \(e\),矛盾
  • \(w(e')=w(e)\),在 \(T\) 中用 \(e\) 换掉 \(e'\),则 \(P\)\(k\) 步中选择边都在 \(T\)
  • 有限步后可把 \(T\) 变为 \(P\) 且权值不变,因此 \(P\) 就是最小生成树

代码

const int inf = 0x3f3f3f3f;
const int N = 110;
int n;
int G[N][N];
int dis[N];
bool vis[N];
int weight;

void prim() {
    fill_n(dis, N, inf);
    dis[1] = 0;

    for (int i = 1; i <= n; i++) {
        int mi = 0; //注意: dis[0] == inf
        for (int j = 1; j <= n; j++)
            if (!vis[j] && dis[i] < dis[mi])
                mi = i;

        if (mi == 0) {
            error("不连通");
            break;
        }

        vis[mi] = true;
        weight += d[mi];
        for (int j = 1; j <= n; j++)
            if (!vis[j] && G[mi][j] < dis[j])
                dis[j] = G[mi][j];
    }
}

Kruskal(稀疏图)

算法

  1. 将所有边按权值 \(w(e)\) 的大小排序
  2. 初始选择边集 \(E=\emptyset\)
  3. 按顺序考虑每条边 \(e\)\(e\) 与已在 \(E\) 中的边不构成环则可选择。\(E=E+{e}\) 若构成环则放弃 \(e\)
  4. 选出 \(n-1\) 条边后 \(E\) 即为一棵最小生成树,否则原图不连通

使用 并查集 维护 过程 3 中的选择

时间复杂度

边排序 \(\Theta(E\log E)\)

并查集 \(\Theta(E\alpha)\)

总复杂度 \(\Theta(E\log E)\)

证明

  • 令 Kruskal 算法得到的树为 \(K\),有一棵最小生成树 \(T\),假设他们不同。
    找到边权最小的在 \(K\) 但不在 \(T\) 中的边 \(e\)
  • \(e\) 加入 \(T\) 中,形成一个环,删掉这个环中一条不在 \(K\) 中的边 \(e'\),得到新生成树 \(T'\)
  • 若不存在 \(e'\)\(K\) 存在环,矛盾
  • \(w(e')>w(e)\),则 \(T'\) 权值和小于 \(T\),矛盾
  • \(w(e')<w(e)\),则 Kruskal 执行时先考虑了 \(e'\),由于成环没加入 \(e'\),因此在 \(e'\) 之前加入的边权值均 \(\le w(e')<w(e)\)。由 \(e\) 的定义,\(K\) 中边权小于 \(w(e)\) 的边均在 \(T\) 中,说明 \(T\) 中边与 \(e'\) 会成环,矛盾
  • \(w(e')=w(e)\),在 \(T\) 中用 \(e\) 换掉 \(e'\)
  • 有限步后可把 \(T\) 变为 \(K\) 且权值不变,因此 \(K\) 就是最小生成树

代码

struct Edge {
    int u, v, w;
     bool operator < (const Edge& edge) const {
        return w < edge.w;
    }
} edges[N], mstEdges[N];
int n, ec, mstec, id[N], weight;

int find(int p) {
    if (id[p] == p) return p;
    return id[p] = find(id[p]);
}

void kruskal() {
    msetc = weight = 0;
    sort(edges, edges + ec);

    for (int i = 1; i <= n; i++) id[i] = i;

    for (int i = 0; i < ec; i++) {
        int p = find(edges[i].u), q = find(edges[i].v);
        if (p == q) continue;

        mstEdges[mstec++] = edges[i];
        weight += edges[i].w;
        id[p] = q;

        if (mstec == n - 1) break;
    }
}
posted @ 2025-11-15 20:42  WA2025  阅读(9)  评论(0)    收藏  举报