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

    Constructing Roads

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 9149    Accepted Submission(s): 3383

    Problem Description
    There are N villages, which are numbered from 1 to N, and you should build some roads such that every two villages can connect to each other. We say two village A and B are connected, if and only if there is a road between A and B, or there exists a village C such that there is a road between A and C, and C and B are connected.
    We know that there are already some roads between some villages and your job is the build some roads such that all the villages are connect and the length of all the roads built is minimum.
     
    Input
    The first line is an integer N (3 <= N <= 100), which is the number of villages. Then come N lines, the i-th of which contains N integers, and the j-th of these N integers is the distance (the distance should be an integer within [1, 1000]) between village i and village j.
    Then there is an integer Q (0 <= Q <= N * (N + 1) / 2). Then come Q lines, each line contains two integers a and b (1 <= a < b <= N), which means the road between village a and village b has been built.
     
    Output
    You should output a line contains an integer, which is the length of all the roads to be built such that all the villages are connected, and this value is minimum.
     
    Sample Input
    3
    0 990 692
    990 0 179
    692 179 0
    1
    1 2
     
    Sample Output
    179
     
    Source
     
    Recommend
    Eddy
     
     

    Prim算法

    1.概览

    普里姆算法Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点英语Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克英语Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆英语Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

    2.算法简单描述

    1).输入:一个加权连通图,其中顶点集合为V,边集合为E;

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

    3).重复下列操作,直到Vnew = V:

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

    b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;

    4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。

    下面对算法的图例描述

    图例说明不可选可选已选(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

    3.简单证明prim算法

    反证法:假设prim生成的不是最小生成树

    1).设prim生成的树为G0

    2).假设存在Gmin使得cost(Gmin)<cost(G0)   则在Gmin中存在<u,v>不属于G0

    3).将<u,v>加入G0中可得一个环,且<u,v>不是该环的最长边(这是因为<u,v>∈Gmin)

    4).这与prim每次生成最短边矛盾

    5).故假设不成立,命题得证.

    4.PRIM代码
     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 using namespace std;
     5 
     6 const int INF=999999999;
     7 const int MAXN=105;
     8 
     9 int n,m;
    10 int map[MAXN][MAXN];
    11 int dis[MAXN];
    12 int visit[MAXN];
    13 
    14 int prim()
    15 {
    16     for(int i=1; i<=n; i++)
    17         dis[i]=INF;
    18     
    19     dis[1]=0;              //起点为顶点1
    20     for(int j=1; j<=n; j++)
    21     {
    22         int t=INF,pos;  //临时变量:最短路径距离t,和该顶点pos
    23         for(int i=1; i<=n; i++)   //查找最短路径'点'
    24         {
    25             if(!visit[i]&&t>dis[i])
    26             {
    27                 t=dis[i];
    28                 pos=i;
    29             }
    30         }
    31         visit[pos]=1;         //最短路径点标记为已经访问
    32         for(int i=1; i<=n; i++) //更新最短路径点
    33         {
    34             //如果,顶点i未被访问过
    35             //且图中新加入的pos点到i点的距离短于原来图到i点的距离
    36             //且pos到i的点有连通(即不为无穷大),则将i点到图的距离更新.
    37             if(!visit[i]&&dis[i]>map[pos][i]&&map[pos][i]!=INF)
    38             {
    39                 dis[i]=map[pos][i];
    40             }
    41         }
    42     }
    43     int temp=0;
    44     for(int k=1; k<=n; k++)
    45     {
    46         temp+=dis[k];
    47     }
    48     return temp;
    49 }
    50 
    51 
    52 int main()
    53 {
    54     int a,b;
    55     while(cin>>n)
    56     {
    57         memset(map,0x3f,sizeof(map));
    58         memset(visit,0,sizeof(visit));
    59         for(int i=1; i<=n; i++)
    60         {
    61             for(int j=1; j<=n; j++)
    62             {
    63                 cin>>map[i][j];
    64             }
    65         }
    66         cin>>m;
    67         while(m--)
    68         {
    69             cin>>a>>b;
    70             map[a][b]=0;
    71             map[b][a]=0;
    72         }
    73         int ans=prim();
    74         cout<<ans<<endl;
    75     }
    76     return 0;
    77 }

    Kruskal算法

    1.概览

    Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,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。最后成功的图就是右:

    3.简单证明Kruskal算法

    对图的顶点数n做归纳,证明Kruskal算法对任意n阶图适用。

    归纳基础:

    n=1,显然能够找到最小生成树。

    归纳过程:

    假设Kruskal算法对n≤k阶图适用,那么,在k+1阶图G中,我们把最短边的两个端点a和b做一个合并操作,即把u与v合为一个点v',把原 来接在u和v的边都接到v'上去,这样就能够得到一个k阶图G'(u,v的合并是k+1少一条边),G'最小生成树T'可以用Kruskal算法得到。

    我们证明T'+{<u,v>}是G的最小生成树。

    用反证法,如果T'+{<u,v>}不是最小生成树,最小生成树是T,即W(T)<W(T'+{<u,v>})。显然T应该包含<u,v>,否则,可以用<u,v>加入到T中,形成一个环,删除环上原有的任意一条边,形成一棵更小权值的生成树。而T-{<u,v>},是G'的生成树。所以W(T-{<u,v>})<=W(T'),也就是W(T)<=W(T')+W(<u,v>)=W(T'+{<u,v>}),产生了矛盾。于是假设不成立,T'+{<u,v>}是G的最小生成树,Kruskal算法对k+1阶图也适用。

    由数学归纳法,Kruskal算法得证。

     4.Kruskal代码

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 using namespace std;
     5 int  fa[1005];
     6 int n,m;
     7 int cc;
     8 struct edge
     9 {
    10     int u,v,w;
    11     void  set(int x,int y,int s)
    12     {
    13         u=x;
    14         v=y;
    15         w=s;
    16     }
    17 
    18 
    19 }e[1000050];
    20 int  find(int x)
    21 {
    22     return x==fa[x]?x:fa[x]=find(fa[x]);
    23 }
    24 bool  cmp(edge a,edge b)
    25 {
    26     return a.w<b.w;
    27 }
    28 void   kruskal()
    29 {
    30     int  ans=0;cc=n;
    31     sort(e,e+n*n,cmp);
    32     for(int i=0; i<n*n; i++)
    33     {
    34         int x=find(e[i].u);
    35         int y=find(e[i].v);
    36         if(x!=y)
    37         {
    38             fa[x]=y;
    39             ans+=e[i].w;
    40             cc--;
    41         }
    42     }
    43     if(cc==1)
    44     printf("%d
    ",ans);
    45 }
    46 
    47 void   init()
    48 {
    49     for(int i=0; i<n; i++)
    50         fa[i]=i;
    51 
    52 
    53 }
    54 int main()
    55 {
    56     while(cin>>n)
    57     {
    58         init();
    59         int cnt=0;int d[105][105];
    60         for(int i=0; i<n; i++)
    61             for(int j=0; j<n; j++)
    62                 scanf("%d",&d[i][j]);
    63         int Q;
    64         cin>>Q;
    65         int a,b;
    66         for(int i=0; i<Q; i++)
    67         {
    68             scanf("%d%d",&a,&b);
    69             d[a-1][b-1]=0;
    70         }
    71         for(int i=0; i<n; i++)
    72             for(int j=0; j<n; j++)
    73             {
    74 
    75                 e[cnt].set(i,j,d[i][j]);
    76 
    77                 cnt++;
    78             }
    79         kruskal();
    80 
    81     }
    82     return 0;
    83 }

    本小白觉得的 这个 跟 最短路有些像呢

  • 相关阅读:
    MySQL 8.0+ 时区问题
    SSM框架整合搭建流程
    最大子段和、最大子矩阵和
    棋盘覆盖(分治)
    石子合并问题
    矩阵连乘
    selenium完成滑块验证
    背包问题(2)
    背包问题(1)
    皇后问题
  • 原文地址:https://www.cnblogs.com/sxy-798013203/p/5288901.html
Copyright © 2011-2022 走看看