zoukankan      html  css  js  c++  java
  • 最小生成树(Prim算法+Kruskal算法)

    什么是最小生成树(MST)?

    给定一个带权的无向连通图,选取一棵生成树(原图的极小连通子图),使生成树上所有边上权的总和为最小,称为该图的最小生成树。

    求解最小生成树的算法一般有这两种:Prim算法和Kruskal算法。

    Prim算法(普里姆算法)

    图的存贮结构采用邻接矩阵。此方法是按各个顶点连通的步骤进行,需要用一个顶点集合,开始为空集,以后将以连通的顶点陆续加入到集合中,全部顶点加入集合后就得到所需的最小生成树。

    简单描述:

      1.初始化:Vnew = {x},其中x为集合V中的任一节点(作为起始点),Enew = {},为空。

      2.在边集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一)。将v加入集合Vnew中,将<u, v>边加入集合Enew中。

      3.重复操作2直至Vnew = V。

    代码展示:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    const int N=101;
    
    int G[N][N];//邻接矩阵
    int Lowest[N];//表示和已选顶点集Vnew的最小距离,Lowest[i]=0表示点i已经在Vnew中
    int n,m;
    
    int prim()
    {
        int Num=0;//最小生成树权值
    
        for(int i=2;i<=n;i++)//选取第一个点开始
            Lowest[i]=G[1][i];//取第一行权值
    
        for(int i=1;i<n;i++)//找到新顶点加入(n-1个)
        {
            int minid=0;
            int mindis=INF;
            for(int j=2;j<=n;j++)//找到距离最小的
            {
                if(Lowest[j]!=0&&Lowest[j]<mindis)
                {
                    mindis=Lowest[j];
                    minid=j;
                }
            }
            Num+=mindis;
            Lowest[minid]=0;//把点minid加入Vnew
    
            for(int j=2;j<=n;j++)//更新Lowest数组
                if(Lowest[j]>G[minid][j])
                    Lowest[j]=G[minid][j];
        }
        return Num;
    }
    
    int main()
    {
        while(~scanf("%d%d",&n,&m))
        {
            memset(G,0x3f,sizeof(G));//初始化为最大值
            for(int i=1;i<=n;i++)//对角线为0
                G[i][i]=0;
    
            int u,v,w;
            for(int i=1;i<=m;i++)
            {
                scanf("%d%d%d",&u,&v,&w);
                G[u][v]=G[v][u]=w;
            }
    
            int MST=prim();//计算最小生成树总权值
    
            printf("%d
    ",MST);
        }
    }
    View Code

    Kruskal算法(克鲁斯卡尔算法

    图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的。该方法对于边相对比较多的不是很实用,浪费时间。思想是贪心思想。

    方法:将图中的边按其权值由小到大的次序顺序选取,若选边后不形成回路,则保留作为一条边,若形成回路则除去。依次选够(n-1)条边,即得最小生成树。(n为顶点数)

    首先我们把所有的边按照权值先从小到大排列,接着按照顺序选取每条边,如果这条边的两个端点不属于同一集合,那么就将它们合并,直到所有的点都属于同一个集合为止。至于怎么合并到一个集合,那么这里我们就可以用到一个工具——-并查集(不知道的同学请移步:Here)。换而言之,Kruskal算法就是基于并查集的贪心算法。

    代码展示:

    #include <cstdio>
    #include <cstdlib>
    #define MAXN 10010
    using namespace std;
    
    int Uset[MAXN];//并查集
    int Rank[MAXN];//
    typedef struct{
        int a, b, price;
    }Node;
    Node edge[MAXN];
    
    int cmp(const void*a, const void *b){
        return ((Node*)a)->price - ((Node*)b)->price;
    }
    
    void Init(int n)//并查集初始化
    {
        for(int i = 0; i < n; i++)
        {
            Rank[i] = 0;
            Uset[i] = i;
        }
    }
    
    int find(int x)
    {
        int root = x;
        while(root != Uset[root]) root = Uset[root];
        while(x != root)
        {
            int t = Uset[x];
            Uset[x] = root;
            x = t;
        }
        return root;
    }
    
    void unionSet(int x, int y)
    {
        x = find(x);
        y = find(y);
        if(Rank[x] > Rank[y])
            Uset[y] = x;
        else {
            Uset[x] = y;
            if(Rank[x] == Rank[y]) Rank[y]++;
        }
    }
    
    int Kruskal(int n, int m)
    {
        int nEdge = 0, res = 0;
    
        qsort(edge, m, sizeof(edge[0]), cmp);//将边按照权值从小到大排序
        for(int i = 0; i < m && nEdge != n - 1; i++)
        {
            if(find(edge[i].a) != find(edge[i].b))//判断当前这条边的两个端点是否属于同一棵树
            {
                unionSet(edge[i].a, edge[i].b);
                res += edge[i].price;
                nEdge++;
            }
        }
        //如果加入边的数量小于m - 1,则表明该无向图不连通,等价于不存在最小生成树
        if(nEdge < n-1) res = -1;
        return res;
    }
    int main()
    {
        int n, m, ans;//n为村庄的数量,m为边的数量
        while(scanf("%d%d", &n, &m)&&n)
        {
            Init(n);
            for(int i = 0; i < m; i++)
                scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].price);
    
            ans = Kruskal(n, m);
            if(ans == -1) printf("?
    ");
            else printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    另外,可以参考:http://blog.csdn.net/luomingjun12315/article/details/47700237

    作者: AlvinZH

    出处: http://www.cnblogs.com/AlvinZH/

    本人Github:https://github.com/Pacsiy/JobDu

    本文版权归作者AlvinZH和博客园所有,欢迎转载和商用,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    452.用最少数量的箭引爆气球
    134.加油站
    Javascript
    spring-JDBC模板
    AOP注解方式ApsectJ开发
    AOP通知类型
    AOP的使用
    AOP相关术语
    AOP
    IOC注解详解
  • 原文地址:https://www.cnblogs.com/AlvinZH/p/6803711.html
Copyright © 2011-2022 走看看