zoukankan      html  css  js  c++  java
  • 最小生成树

    算法思想 来自:https://www.bilibili.com/video/BV1Ut411976J?from=search&seid=6406725088142163166

    一,MST 性质 (Minimum Spaning Tree)

    应该知道 最小生成树 并不唯一。 (•‾̑⌣‾̑•)✧˖°

    定义:设 N = (V,E )是一个连通网,U 是顶点集V的一个非空子集。

    若(u , v )是一条具有最小权值(代价)的边,其中u∈U, v∈V - U,则必存在一棵包含边(u,v)的最小生成树。 

    这是构造最小生成树的基本性质。

    二,Prim 算法

    ① 算法思想

    利用 MST 性质,将图中的点分为两个集合:

    U:已落在生成树上的顶点集、

    U-V:未落在生成树上的顶点集

    接下来就在所有 连通U中的顶点 和 V-U中的顶点 的边中选取 权值最小的边

    (解释:U 是未完成的最小生成树,要想所有点连接,则最小生成树必存在边 (U 中一点,V-U 中一点),所以他们中最小的一定在树中)

    ② 步骤

    设 N = (V,E )是一个连通网,TE 是 N 上最小生成树中 边的集合

    1,初始化 :令 U={ u0 },TE={ }

    2,选择:在所有 u 属于 U,v 属于 V-U 的边(u,v)中,找一条代价最小的边(u0,v0)

    3,更新:将(u0,v0)并入集合 TE 中,同时 v0 并入 U

    4,重复 2,3 操作,直至 U=V,则 T = { V,TE } 为 N 的最小生成树

    ③ 例题

     

    步骤:其中 星号:表示该边已经加入最生,打勾:表示在本次比较中找到的 要加入最生的

    ④ 代码

    用链式前向星

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define inf 0x3f3f3f3f
    #define N 10010
    int last[N], tot;
    struct edge
    {
        int to;
        int w;
        int pre;
    }e[N];
    void init()
    {
        memset(last, -1, sizeof(last));
        tot = 0;
    }
    void add(int from, int to, int w)
    {
        tot++;
        e[tot].to = to;
        e[tot].w = w;
        e[tot].pre = last[from];
        last[from] = tot;
    }
    int vis[N];  //(vis[i]==1)为  集合 U  ,(vis[i]==0)为 集合 V-U
    int dis[N];  // 记录 U 集合中的点 到 V-U 的所有点的 最短距离.  如 dis[2]=8 代表所有在 U 中的点 到 点2 的最短距离是 8,这里边的另外一点为 p[2]
    int p[N];
    int Prim(int n)
    {
        // 1,初始化 :令 U={ u0 },TE={ },用 p[]记录路径, 这里没用
        for (int i = 1; i <= n; i++) 
        {
            vis[i] = 0;
            dis[i] = inf;   // 既然是要记录最短的距离,则初始化 为 inf
            p[i] = 0;
        }
        int s = 1;      //  开始的结点 u0
        vis[s] = 1;  // U={ u0 }
        p[s] = -1;    // 标记 根
    
        // 4,重复 2,3 操作,直至 U=V,则 T = { V,TE } 为 N 的最小生成树
        int sum = 0;     // 记录总代价,可以看作是 TE
        int m = n - 1;  // 最小生成树的边数
        while (m--)    // 最小生成树的边数为原来图的点数减一,即  U == V,
        {
            for (int i = last[s]; ~i; i = e[i].pre)  // 2,选择:在所有 u 属于 U,v 属于 V-U 的边(u,v)中,找一条代价最小的边(u0,v0)
            {
                int to = e[i].to;
                int w = e[i].w;
                if (!vis[to] && dis[to] > w)   // 更新 U 集合中的点 到 V-U 的所有点的 最短距离 如果这条路走到尽头, 下面还是会选出 最短的那条路径,直接跳过去
                {
                    dis[to] = w;
                    p[to] = s;    // 记录路径
                }
            }   
            int min = inf;
            for (int j = 1; j <= n; j++)   // 找到 在所有 u 属于 U,v 属于 V-U 的边(u,v)中,代价最小的边( p[j] ,j)
            {
                if (!vis[j] && dis[j] < min)
                {
                    min = dis[j];
                    s = j;
                }
            }
            if (min == inf)   // 如果没有这条边 说明不是 连通图
                return 0;
    
            //   3,更新:将(u0,v0)并入集合 TE 中,同时 v0 并入 U;
            vis[s] = 1;     // v0 并入 U
            sum += min;    // (u0,v0)并入集合 TE 中
        }
        return sum;
    }
    
    int main(void)
    {
        int n, m;    // m 是边数, n 是点数
        while (scanf("%d%d", &m, &n) == 2 && m)
        {
            init();
            for (int i = 0; i < m; i++)
            {
                int u, v, w; scanf("%d%d%d", &u, &v, &w);
                add(u, v, w);
                if (u != v)
                    add(v, u, w);
            }
            int sum = Prim(n);
            if (sum == 0)
                puts("?");
            else
                printf("%d
    ", sum);
        }
        return 0;
    }
    /*
    测试数据;长方形
    4 4
    1 2 1
    1 3 4
    2 4 1
    3 4 4
    */
    View Code

     用优先队列

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<queue>
    #include<string.h>
    using namespace std;
    #define N 105
    struct Edge
    {
        int pre, to, w;
    }e[N];
    int last[N], vis[N], p[N];
    int tot = 0, n, m;    // m 是边数, n 是点数
    void add(int from, int to, int w)
    {
        tot++;
        e[tot].to = to;
        e[tot].w = w;
    
        e[tot].pre = last[from];
        last[from] = tot;
    }
    void init()
    {
        memset(last, -1, sizeof(last));
        memset(vis, 0, sizeof(vis));
        memset(p, 0, sizeof(p));
        tot = 0;
    }
    struct Node
    {
        int to;
        int cost;
        int from;  /* 因为 找 最小的边 在队列里面完成了,只有出队列后,我们才知道是哪条边,不过那时候我们也不知道这条边的 起点 是哪一个
                      所以为了可以记录路径,在这里增加一个变量,记录 边的起点  */
        Node() {}
        Node(int tt, int cc,int qq) :to(tt), cost(cc), from(qq){}
        friend bool operator < (const Node &a, const Node &b)
        {
            return a.cost > b.cost;
        }
    };
    void prim()
    {
        int cnt = 0, ans = 0;
    
        priority_queue<Node>q;
        q.push(Node(1, 0, -1));
        p[1] = -1;   // 标记 根结点
        while (q.size())  // BFS
        {
            Node vertex = q.top();
            q.pop();
    
            if (vis[vertex.to] == 0)
            {
                vis[vertex.to] = 1;
                ans += vertex.cost;
                p[vertex.to] = vertex.from;
                cnt++;
                for (int i = last[vertex.to]; ~i; i = e[i].pre)   // 起点在 点集 T 中,终点在 E-T 中 
                {
                    Node next = { e[i].to,e[i].w ,vertex.to };
                    if (vis[next.to] == 0)
                    {
                        q.push(next);
                    }
                }
            }
        }
        if (cnt != n)
            puts("?");
        else
            printf("%d
    ", ans);
    }
    int main(void)
    {
        while (scanf("%d%d", &m, &n) == 2 && m)
        {
            init();
            for (int i = 0; i < m; i++)
            {
                int u, v, w; scanf("%d%d%d", &u, &v, &w);
                add(u, v, w);
                if (u != v)
                    add(v, u, w);
            }
            prim();
    
        }
        return 0;
    }
    View Code

    用 邻接矩阵

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define inf 0x3f3f3f3f
    #define N 105
    int a[N][N], vis[N], dis[N], p[N];
    int n, m;  // m 是边数, n 是点数
    void init() 
    {
        memset(vis, 0, sizeof(vis));
        memset(dis, 0x3f, sizeof(dis));
        memset(a, 0x3f, sizeof(a));
        memset(p, 0, sizeof(p));
    }
    int prim()
    {
        int s = 1;
        dis[1] = 0, vis[s] = 1, p[s] = -1;
        int sum = 0;
        for (int i = 1; i < n; i++)  // 边数 为点数减一
        {
            for (int j = 1; j <= n; j++)  // 更新距离,标记起点
            {
                if (vis[j] == 0 && dis[j] > a[s][j])
                {
                    dis[j] = a[s][j];
                    p[j] = s;
                }
            }
            int min = inf;
            for (int j = 1; j <= n; j++)  // 找到 最小的那条边
            {
                if (vis[j] == 0 && dis[j] < min)
                {
                    min = dis[j];
                    s = j;
                }
            }
            if (min == inf)
                return 0;
            vis[s] = 1;
            sum += min;
        }
        return sum;
    }
    int main(void)
    {
        while (scanf("%d%d", &m, &n) == 2 && m)
        {
            init();
            for (int i = 1; i <= m; i++)
            {
                int u, v, w; scanf("%d%d%d", &u, &v, &w);
                a[u][v] = a[v][u] = w;
            }
            int sum = prim();
            if (sum == 0)
                puts("?");
            else
                printf("%d
    ", sum);
        }
        system("pause");
        return 0;
    }
    View Code

     在这里提醒一句:注意起点的判断,虽然这边都可以省略这一步。

     三,Kruskal

     ①  算法思想

    设 连通网 N = {V,E }, 令 最小生成树 初始状态为 只有 n 个顶点 而 没有边 的非连通图 T = {V,{ }},

    每个顶点自成一个连通分量 ,

    然后在 E 中,选取 代价最小 的边,若 该边的顶点 落在 T 中的不同的连通分量上(即不能形成 环)

    则将该边加入到  T 中,否则,舍去该边,选择下一条边。

    依次类推,直至 T 中的所有顶点 都在  同一个连通分量上。

     ② 步骤

    顶点数可以直接用 cnt 数,所以可以不用初始化处理。

    1,排序  将所有的边 按照权值 由小到大 排序

    2,选择  将排序好的所有边依次加入生成树中,会形成 环 的跳过(用并查集)

    3,判断  判断最后会不会形成 最小生成树(即 最小生成树 的边数 是否是 该图 的点数-1 )

    ③ 例题

    ④ 代码

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<algorithm>
    using namespace std;
    #define N 110
    int p[N], num[N];
    struct edge
    {
        int from, to;
        int w;
    }e[5000];
    int cmp(edge &a, edge &b)  // 好像有取值 会更快
    {
        return a.w < b.w;
    }
    int find(int x)
    {
        int a = x;
        while (x != p[x])
            x = p[x];
        while (a != x)
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }
    int join(int x, int y)
    {
        x = find(x), y = find(y);
        if (x == y)
            return 0;
        p[x] = y;
        num[y] += num[x];
        return 1;
    }
    int main(void)
    {
        int n;    // 边数
        int m;    // 点数
        while (scanf("%d%d", &n, &m), n)
        {
            for (int i = 1; i <= n; i++)
                scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].w);
            for (int i = 1; i <= m; i++)
            {
                p[i] = i;
                num[i] = 1;
            }
            sort(e + 1, e + n + 1, cmp);    // 1,排序  将所有的边 按照权值 由小到大 排序
    
            int sum = 0;   // 算 最小生成树 的代价
            int t = 0;     // 数 最小生成树 的边数
            for (int i = 1; i <= n; i++)    // 2,选择  将排序好的所有边依次加入生成树中,会形成 环 的跳过(用并查集判断)
            {
                if (join(e[i].from, e[i].to) == 1)  // if 不加大括号,只读取一句,即使你写同一行,后面的语句也不属于 if
                {
                    sum += e[i].w; 
                    t++;
                }
            }
            if (num[find(1)] == m)   // 3,判断 生成树的边数 是否 等于 点数
            //if (t == m - 1)   
                printf("%d
    ", sum);
            else
                puts("?");
        }
    
        system("pause");
        return 0;
    }
    /*
    测试数据:
    10 6
    1 2 6
    1 4 5
    1 3 1
    2 3 5
    3 4 5
    2 5 3
    3 5 6
    3 6 4
    4 6 2
    5 6 5
    */
    View Code

     四,prim 和 Kruskal 比较  (҂‾ ▵‾)︻デ═一 - - -  - -  -

    Prim :   通过 点 去选择   O( n 的平方 )(n  为顶点数) 适合稠密图

    Kruskal:通过 边 去选择   O( eloge )  ( e 为边数 )    适合稀疏图

    ========= ======== ======= ======= ====== ===== ==== === == =

      所以我时常害怕,愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者流的话。

    能做事的做事,能发声的发声。有一分热,发一分光,就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。 

      此后如竟没有炬火:我便是唯一的光。倘若有了炬火,出了太阳,我们自然心悦诚服的消失,不但毫无不平,

    而且还要随喜赞美这炬火或太阳;因为他照了人类,连我都在内。

                                         ——《热风·随感录四十一》 鲁迅

  • 相关阅读:
    Firefly 3288又一次制作android和lubuntu双系统固件
    想做一个完美的健身训练计划,你须要知道什么?
    【LeetCode-面试算法经典-Java实现】【075-Sort Colors (颜色排序)】
    每天进步一点点——Ganglia的Python扩展模块开发
    Unity3D-rigidBody.velocity
    泛型初识
    HDOJ 5418 Victor and World 状压DP
    UIPopoverController具体解释
    怎样提升站点的性能?
    PHP操作MongoDB数据库具体样例介绍(增、删、改、查) (六)
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/13020254.html
Copyright © 2011-2022 走看看