📂
🔖图论
2022-02-17 22:21阅读: 164评论: 0推荐: 0

最小生成树

最小生成树介绍

在介绍最小生成树前,先介绍一下生成树:在一张联通无向图中,我们取图上的所有点,并取最少的边将其相连使其连通生成一棵树,这个树就被称作这张图的生成树。因为树的边数一定是点数-1,所以就是取 n1 条边来连通 n 个点。

那么最小生成树(Minimum Spanning Tree),是最小权重生成树的简称。规定树的权值为树上所有边的权值和,那么它就是一张连通加权无向图中一颗权值最小的生成树,如上图。由定义可以看出,最小生成树不一定唯一


对于如何生成最小生成树的问题,我们有两种常见的解决方法,分别是Prim算法和Kruskal算法,两者都基于贪心。

Prim算法

给定图 G(V,E) ,我们逐步进行Prim算法,假设在过程中,Vnew 表示已经选中作为生成树上的结点,Enew 表示已经选中作为最小生成树上的边。

规定Prim算法如下:

  1. 初始化一个结点 x 加入 Vnew ,则 Vnew=x 。由于最小生成树包含所有节点,我们可以用任意一个结点初始化。
  2. 从集合 E 中选择权值最小的边 (u,v) ,满足 uVnewvVnew ,将 v 加入集合 Vnew 中,把 (u,v) 加入 Enew 中。
  3. 重复操作,直到所有点都已经被选中加入最小生成树中,即 Vnew=V
  4. 根据 VnewEnew 所得到的新图 Gnew(Vnew,Enew) 即为原图 G(V,E) 的最小生成树。

只需要证明根据第二步所得到边一定为最优解即可。

  1. 按照Prim算法得到的第一条边一定是最小生成树上的边。

    如果不是,我们把这一条边加入到最小生成树中,形成回路,我们让最小的边取代回路中比它大的边,得到权值更小的生成树。所以第一条边一定是最小生成树上的边。

  2. 假设在某一个步骤中,Prim得到的点集为 Vnew={v1,v2,v3vs1} 。 根据Prim算法,我们应该选择与这些点有交集的边中,权值最小的边。

    假设这个权值最小的边连接 Vnew 中的 vk ,如果不选择这条边,那么我们把这条边加入最小生成树中,形成一个回路,且这个回路包含 vk ,我们假设连接 vk 的那条边另一端为 vi ,我们用权值最小的边替换掉 (vk,vi) ,得到的生成树权值一定不大于最小生成树,因此选择这条边为最优边。

int h[N], e[M], w[M], ne[M], idx; // 邻接表存图
bool st[N]; // st(x) 表示x点已经加入生成树中
int dist[N]; // dist(x) 表示x点距离已经生成的树的最小距离

int prim () // 返回最小生成树的权值
{
    int ret = 0;
    memset(d, 0x3f, sizeof d); d[1] = 0; // 初始化从1开始,最开始生成树上的V和E都为空
    for (int i = 0; i < n; i ++ ) // 要加入n个点,迭代n次,每次放进一个点
    {
        // 找出距离当前生成树最近的点
        int t = -1;
        for (int k = 1; k <= n; k ++ )
            if (!st[k] && (t == -1 || dist[k] < dist[t])) t = k;
        
        // 把选择的点加入生成树中
        st[t] = true;
        ret += dist[t];
        
        // 由于加入了一个点,那么其他点也可以通过连接这个点到达生成树,更新dist
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            dist[j] = min(dist[j], w[i]);
        }
    }
    return ret; // 返回最小生成树的权值
}

有没有觉得Prim算法和Dijkstra算法雷同?没错,它们都是根据同样的贪心思想,唯一的区别仅仅在于更新的时候。

Prim算法复杂度:根据上述代码,复杂度为 O(n2+2E) ,由于图上每个点至多被更新 1 次,所以图的所有边至多被更新 2 次。

与Dijsktra算法一样,Prim算法可以使用二叉堆优化,复杂度降到 O((n+2E)logn)


Kruskal 算法

如果说Prim算法是小树长成大树的过程,那么Kruskal算法就是拼图的过程。

Prim算法基于点来扩大树,而Kruskal基于边来扩大,具体来说,该算法的过程如下:

  1. 将图 G(V,E) 的所有边按权值进行非递减排序。
  2. 初始化每个点都为单独的连通分量。(因为此时我们还没有选择边作为最小生成树的一部分)
  3. 从后往前检查所有边(u,v)
    • uv 在同一个连通分量里,那么加入 (u,v) 会产生环,因此不能选择。
    • uv 不在一个连通分量里,那么加入 (u,v) 一定是最优的。如果不加入,形成生成树 T ,把 (u,v) 加入 T 中,会形成环,环中包含 (u,v) 和另外一条权值不小于 (u,v) 的边,我们把这条边用 (u,v) 替换,不会使结果变差,因此 (u,v) 是最优的选择。

因为会考虑所有边,因此一定能构成一颗完整的最小生成树(除非原图不连通)。

在Kruskal算法中,最关键的地方在于“连通”分量的查询和合并,需要知道两个点是否在一个连通分量中,以及如果不是在一个连通分量,需要将其合并,我们可以使用并查集来支持此操作。

struct Edge {
    int u, v, d; // 表示边两端的点以及边权
    bool operator < (const Edge & rhs) const { // 重载小于号,支持比较
        return d < rhs.d;
    }
}

int p[N];
Edge edges[M];
int n, m;

int find (int x) { return p[x] == x ? x : p[x] = find(p[x]); }

int kruskal ()
{
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    sort(edges + 1, edges + m + 1); // 假设边集从1开始存储
    
    int ret = 0, cnt = 0; // cnt表示目前已经选择了多少条边(生成树只需要n-1条边)
    for (int i = 1; i <= m && cnt < n - 1; i ++ )
    {
        int u = edges[i].u, v = edges[i].v, d = edges[i].d;
        u = find(u), v = find(v);
        if (u == v) continue;
        ++ cnt, res += d, p[u] = v;
    }
    
    if (cnt != n - 1) return -1; // 原图不连通,没有生成树,何谈最小
    return ret;
}

本文作者:Horb7

本文链接:https://www.cnblogs.com/Horb7/p/15906687.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Horb7  阅读(164)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.