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

    关于图的几个概念定义:

            

    关于图的几个概念定义:

    • 连通图:在无向图中,若任意两个顶点vi与vj都有路径相通,则称该无向图为连通图。
    • 强连通图:在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图。
    • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
    • 生成树一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
    • 最小生成树在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

    构造最小生成树的准则有3条:

    (1)必须只使用该网络中的边来构造最小生成树。

    (2)必须使用且仅使用n-1条边来连接网络中的n个顶点。

    (3)不能使用产生回路的边。


    这里写图片描述


    下面介绍两种求最小生成树算法

    1 Prim(普利姆算法)算法--加点法

    此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

    Prim算法从任意一个顶点开始,每次选择一个与当前顶点集最近的一个顶点,并将两顶点之间的边加入到树中。Prim算法在找当前最近顶点时使用到了贪婪算法。

    实现过程:

     

    图例说明不可选可选已选(Vnew
     

    此为原始的加权连通图。每条边一侧的数字代表其权值。 - - -

    顶点D被任意选为起始点。顶点ABEF通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 C, G A, B, E, F D
     

    下一个顶点为距离DA最近的顶点。BD为9,距A为7,E为15,F为6。因此,FDA最近,因此将顶点F与相应边DF以高亮表示。 C, G B, E, F A, D
    算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 C B, E, G A, D, F
     

    在当前情况下,可以在CEG间进行选择。CB为8,EB为7,GF为11。E最近,因此将顶点E与相应边BE高亮表示。 C, E, G A, D, F, B
     

    这里,可供选择的顶点只有CGCE为5,GE为9,故选取C,并与边EC一同高亮表示。 C, G A, D, F, B, E

    顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG G A, D, F, B, E, C

    现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 A, D, F, B, E, C, G
     1 #include<stdio.h>
     2 #include<string.h>
     3 #define MAX 0x3f3f3f3f
     4 using namespace std;
     5 int logo[1010];///用0和1来表示是否被选择过
     6 int map1[1010][1010];
     7 int dis[1010];///记录任意一点到这一点的最近的距离
     8 int n,m;
     9 int prim()
    10 {
    11     int i,j,now;
    12     int sum=0;
    13     for(i=1;i<=n;i++)///初始化
    14     {
    15         dis[i]=MAX;
    16         logo[i]=0;
    17     }
    18     for(i=1;i<=n;i++)
    19     {
    20         dis[i]=map1[1][i];
    21     }
    22     dis[1]=0;
    23     logo[1]=1;
    24     for(i=1;i<n;i++)///循环查找
    25     {
    26         now=MAX;
    27         int min1=MAX;
    28         for(j=1;j<=n;j++)
    29         {
    30             if(logo[j]==0&&dis[j]<min1)
    31             {
    32                 now=j;
    33                 min1=dis[j];
    34             }
    35         }
    36         if(now==MAX)///防止不成图
    37         {
    38             break;
    39         }
    40         logo[now]=1;
    41         sum=sum+min1;
    42         for(j=1;j<=n;j++)///填入新点后更新最小距离,到顶点集的距离
    43         {
    44             if(logo[j]==0&&dis[j]>map1[now][j])
    45             {
    46                 dis[j]=map1[now][j];
    47             }
    48         }
    49     }
    50     if(i<n)
    51     {
    52         printf("?
    ");
    53     }
    54     else
    55     {
    56         printf("%d
    ",sum);
    57     }
    58 }
    59 int main()
    60 {
    61     while(scanf("%d%d",&m,&n)!=EOF)///n是点数
    62     {
    63         if(m==0)
    64         {
    65             break;
    66         }
    67         memset(map1,0x3f3f3f3f,sizeof(map1));///map是邻接矩阵储存图的信息
    68         for(int i=0;i<m;i++)
    69         {
    70             int a,b,c;
    71             scanf("%d%d%d",&a,&b,&c);
    72             if(c<map1[a][b])///防止出现重边
    73             {
    74                 map1[a][b]=map1[b][a]=c;
    75             }
    76         }
    77         prim();
    78     }
    79     return 0;
    80 }

    邻接表实现:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<vector>
     4 #include<algorithm>
     5 #define INF 0x3f3f3f3f
     6 using namespace std;
     7 struct node
     8 {
     9     int end;///终点
    10     int power;///权值
    11 } t;
    12 int n;///n为点数
    13 vector<node>q[500001];///邻接表储存图的信息
    14 int dis[500001];///距离
    15 int vis[500001];///标记数组
    16 void  prime()
    17 {
    18     int i,len,j,pos,sum,start;
    19     memset(vis,0,sizeof(vis));
    20     sum=0;
    21     start=1;///任意取起点
    22     for(i=0; i<=n; i++)
    23     {
    24         dis[i]=INF;
    25     }
    26     len=q[start].size();
    27     for(i=0; i<len; i++)///从任意起点开始的dis数组更新
    28     {
    29         if(q[start][i].power<dis[q[start][i].end])
    30         {
    31             dis[q[start][i].end]=q[start][i].power;
    32         }
    33     }
    34     vis[start]=1;
    35     for(j=0; j<n-1; j++)
    36     {
    37         int pos,min=INF;
    38         for(i=1; i<=n; i++)
    39         {
    40             if(vis[i]!=0&&dis[i]<min)
    41             {
    42                 min=dis[i];
    43                 pos=i;///找到未访问节点中权值最小的
    44             }
    45         }
    46         if(pos==INF)///防止不成图
    47         {
    48             break;
    49         }
    50         vis[pos]=1;
    51         sum=sum+min;
    52         len=q[pos].size();///再次更新dis数组
    53         for(j=0; j<len; j++)
    54         {
    55             if(vis[q[pos][j].end]==0&&dis[q[pos][j].end]>q[pos][j].power)
    56             {
    57                 dis[q[pos][j].end] = q[pos][j].power;
    58             }
    59         }
    60     }
    61     if(j<n)
    62     {
    63         printf("?
    ");
    64     }
    65     else
    66     {
    67         printf("%d
    ",sum);
    68     }
    69 }
    70 int main()
    71 {
    72     int m,i;
    73     int begin,end,power;
    74     int a,b;
    75     while(scanf("%d%d",&n,&m)!=EOF)
    76     {
    77         for(i=0; i<=n; i++)
    78         {
    79             q[i].clear();///将victor数组清空
    80         }
    81         for(i=0; i<m; i++)
    82         {
    83             scanf("%d%d%d",&begin,&end,&power);///输入
    84             t.end=end;
    85             t.power=power;
    86             q[begin].push_back(t);
    87             t.end=begin;///无向图
    88             t.power=power;
    89             q[end].push_back(t);
    90         }
    91         prime();
    92     }
    93     return 0;
    94 }

     这里再给出一个没有使用标记数组的代码:

    int prim(int s)
    {
        int i,j,sum=0;
        int now;
        for(i=1;i<=n;i++)
        {
            closest[i]=INT_MAX;
        }
        for(i=1;i<=n;i++)
        {
            closest[i]=map[s][i];
        }
        closest[s]=0;
        for(i=1;i<n;i++)//这里的i代表的是边数,有n个点就会有n-1条边
        {
            int min=INT_MAX;
            for(j=1;j<=n;j++)
            {
                if(closest[j]&&closest[j]<min)
                {
                    min=closest[j];
                    now=j;//找到所需的最小边
                }
            }
            sum+=min;
            closest[now]=0;//将找到的边加入到最小生成树之中
            for(j=1;j<=n;j++)//找到新的点加入已选点集合之后,更新该点到未选点集合的距离
            {
                if(map[now][j]&&map[now][j]<closest[j])
                {
                    closest[j]=map[now][j];
                }
            }
        }
        return sum;
    }

    Kruskal(克鲁斯卡尔)算法--加边法

    1.概览

      Kruskal算法是一种用来寻找最小生成树的算法,在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。

    2.实现过程

    1).记Graph中有v个顶点,e个边

    2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边

    3).将原图Graph中所有e个边按权值从小到大排序

    4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中  if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中   添加这条边到图Graphnew

      图例描述:

    首先第一步,我们有一张图Graph,有若干点和边 

    将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完成后,我们率先选择了边AD。这样我们的图就变成了下图

      

    在剩下的变中寻找。我们找到了CE。这里边的权重也是5

    依次类推我们找到了6,7,7,即DF,AB,BE。

    下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示了)。最后就剩下EG和FG了。当然我们选择了EG。

    代码:(利用并查集来实现)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int n,m,sum;
    struct node
    {
        int start;///起点
        int end;///终点
        int power;///权值
    }edge[5050];
    int pre[5050];
    int cmp(node a,node b)
    {
        return a.power<b.power;///按权值排序
    }
    int Find(int x)///并查集找祖先
    {
        if(x!=pre[x])///递归法
        {
            pre[x]=Find(pre[x]);
        }
        return pre[x];
    
        /*int a;///循环法
        a=x;
        while(pre[a]!=a)
        {
            a=pre[a];
        }
        return a;*/
    }
    void Union(int x,int y,int n)
    {
        int fx =Find(x);
        int fy =Find(y);
        if(fx!=fy)
        {
            pre[fx]=fy;
            sum+=edge[n].power;
        }
    }
    int main()
    {
        int i;
        while(scanf("%d",&n)!=EOF)
        {
            sum=0;
            m=n*(n-1)/2;
            for(i=1;i<=m;i++)
            {
                scanf("%d%d%d",&edge[i].start,&edge[i].end,&edge[i].power);
            }
            for(i=1;i<=m;i++)///并查集的初始化
            {
                pre[i]=i;
            }
            sort(edge+1,edge+m+1,cmp);
            for(i=1;i<=m;i++)
            {
                Union(edge[i].start,edge[i].end,i);
            }
            printf("%d
    ",sum);
        }
        return 0;
    }
  • 相关阅读:
    python模块—socket
    mac os系统的快捷键
    教你如何将UIImageView视图中的图片变成圆角
    关于ASP.NET MVC
    iOS 日期格式的转换
    将App通过XCode上传到AppStore 出现这个错误“An error occurred uploading to the iTunes Store”的解决方法
    关于MAC OS下面两个软件的功能改进——Dictionary和Fit 输入法
    分享一下上个星期的香港行程
    【博客园IT新闻】博客园IT新闻 iPhone 客户端发布
    解决Entity Framework Code First 的问题——Model compatibility cannot be checked because the database does not contain model metadata
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/9140591.html
Copyright © 2011-2022 走看看