最小生成树模板

来源:互联网 发布:win10写字板源码 编辑:程序博客网 时间:2024/06/05 06:22

1.prim算法

  基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

   在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

   此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

   Prim算法的核心:始终保持TE中的边集构成一棵生成树

注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。

示例如下:

 

(1)图中有6个顶点v1-v6,每条边的边权值都在图上;在进行prim算法时,我先随意选择一个顶点作为起始点,当然我们一般选择v1作为起始点,好,现在我们设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边,现在状态如下:

U={v1}; TE={};

(2)现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。

通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:

U={v1,v3}; TE={(v1,v3)};

(3)继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。

我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:

U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循环一下直到找到所有顶点为止。


prim算法:

要求节点数量N不超过5000

#include <stdio.h>#include <stdlib.h>#include <iostream>#define N 55#define INF 1<<30using namespace std;int n,m,map[N][N];void input(){    int i,j,x,y,p,len;    cin>>m;    for(i=1; i<=n; i++)        for(j=1; j<=n; j++)            map[i][j] = INF;    for(i=1; i<=m; i++)    {        cin>> x >> y >> len;        if( map[x][y] > len )            map[x][y] = map[y][x] = len ;    }}void prim(){    int vis[N],dis[N],i;    for(i=1;i<=n;i++)    {        vis[i]=0;        dis[i]=INF;    }    int k;    int now=1;    vis[now]=1;    dis[now]=0;    for(k=1;k<=n;k++)    {        for(i=1;i<=n;i++)        {            if(!vis[i]&&map[now][i]!=INF)            {                if(dis[i]>map[now][i])dis[i]=map[now][i];            }        }        int min=INF;        for(i=1;i<=n;i++)        {            if(!vis[i]&&dis[i]<min){                now=i;                min=dis[i];            }        }vis[now]=1;    }    int sum=0;    for(i=1;i<=n;i++)    {        sum+=dis[i];    }    printf("%d\n",sum);}int main(){  //  freopen("test.in","r",stdin);    while( cin>>n && n )    {        input();        prim();    }    return 0;}

应用题目          数据量        耗时       内存

ZOJ1372 

ZOJ1406



另一个算法下次写


ZOJ1406

和上题一样,初始化不要忘了,1y

#include <cstdio>#include <cstring>#include <iostream>using namespace std;#define N 30#define INF 1<<30int n,m;int map[N][N];void prim(){    int vis[N],dis[N],i;    for(i=1;i<=n;i++)    {        vis[i]=0;        dis[i]=INF;    }    int k;    int now=1;    vis[now]=1;    dis[now]=0;    for(k=1;k<=n;k++)    {        for(i=1;i<=n;i++)        {            if(!vis[i]&&map[now][i]!=INF)            {             //   printf("   %d %d %d\n",now,i,map[now][i]);                if(dis[i]>map[now][i])dis[i]=map[now][i];            }        }        int min=INF;        for(i=1;i<=n;i++)        {            if(!vis[i]&&dis[i]<min){                now=i;                min=dis[i];            }        }vis[now]=1;    }    int sum=0;    for(i=1;i<=n;i++)    {        sum+=dis[i];      //  printf("%d %d\n",i,dis[i]);    }    printf("%d\n",sum);}int main(){    //freopen("test.in","r",stdin);    char ch1[2],ch2[2];    int d,p1,p2,i,j;    while(cin>>n&&n)    {        for(i=1;i<=n;i++)        {            for(j=1;j<=n;j++)map[i][j]=INF;//这里的初始化不要忘记        }        for(i=1;i<=n-1;i++)        {            cin>>ch1;            cin>>m;            p1=ch1[0]-'A'+1;            for(j=1;j<=m;j++)            {                cin>>ch2>>d;                p2=ch2[0]-'A'+1;                map[p1][p2]=d;                map[p2][p1]=d;           //     printf("%d %d\n",p1,p2);            }        }        prim();    }    return 0;}

POJ2485 最小生成树的最长边是所有生成树中最短的

纯套模板

关于最小生成树的最长边为所求的证明

因为题目要求没两个点间必须存在通路。故满足条件的任意方案为一生成树(spanning tree)1.先证最小生成树T的最长边(u,v)小于等于任一生成树T的最长边反证:假设不满足上述命题,即:存在一个生成树T',其最长边小于(u,v)则有T'的任一边 <= T’最长边 < (u,v),因为T'是生成树,则其上u,v两点间必存在唯一通路P;由于u,v位于(u,v)所通过的某割的两边,P上必有一边(x,y)通过割且(x,y)<(u,v)在T上去掉(u,v)加上(x,y)形成T''可证T‘’仍然是生成树且所有边权值和小于T的权值和。与T是最小生成树矛盾。得证2.再证存在一个可达的方案,其最长边等于(u,v),即1中的下限是紧确的。 显然,只要选取最小生成树为所选方案即可得综上所述,最小生成树的最长边即为题目所述任一生成树的最长边的最小值

#include <stdio.h>#include <stdlib.h>#include <iostream>#define N 550#define INF 1<<30using namespace std;int n,m,map[N][N];void input(){    int i,j,p,len;    scanf("%d",&n);    for(i=1; i<=n; i++)        for(j=1; j<=n; j++)            map[i][j] = INF;    for(i=1; i<=n; i++)    {        for(j=1; j<=n; j++){            scanf("%d",&len);            if( len< map[i][j] )                map[i][j] = map[j][i] = len ;        }    }}void prim(){    int vis[N],dis[N],i;    for(i=1;i<=n;i++)    {        vis[i]=0;        dis[i]=INF;    }    int k;    int now=1;    vis[now]=1;    dis[now]=0;    for(k=1;k<=n;k++)    {        for(i=1;i<=n;i++)        {            if(!vis[i]&&map[now][i]!=INF)            {                if(dis[i]>map[now][i])dis[i]=map[now][i];            }        }        int min=INF;        for(i=1;i<=n;i++)        {            if(!vis[i]&&dis[i]<min){                now=i;                min=dis[i];            }        }vis[now]=1;    }    int ma=0;    for(i=1;i<=n;i++)    {        ma=ma>dis[i]?ma:dis[i];    }    printf("%d\n",ma);}int main(){  //  freopen("test.in","r",stdin);    int t;    scanf("%d",&t);    while(t--)    {        input();        prim();    }    return 0;}


Kruskal算法

算法简单描述

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。最后成功的图就是右:

 

kruskal模板:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<vector>#include<map>using namespace std;#define N 50005int n,m,pre[N];map<string,int>id;struct ss{    int x,y,len;}node[N];bool cmp(ss a,ss b){    return a.len<b.len;}void input(){    int k=1;    string s1,s2;    char s11[10],s22[10];    id.clear();    scanf("%d%d",&n,&m);    for(int i=0;i<m;i++){            scanf("%s%s%d",s11,s22,&node[i].len);            s1=s11;            s2=s22;            if(!id[s1])id[s1]=k++;            if(!id[s2])id[s2]=k++;            node[i].x=id[s1];            node[i].y=id[s2];    }    sort(node,node+m,cmp);    //for(int i=0;i<m;i++)printf("%d %d %d\n",node[i].x,node[i].y,node[i].len);}int find(int x){    if(x!=pre[x])pre[x]=find(pre[x]);    return pre[x];}int kruskal(int t){    int a,b,cnt=0,sum=0;;    for(int i=1;i<=n;i++)pre[i]=i;    for(int i=t;i<m;i++)    {        a=find(node[i].x);        b=find(node[i].y);        if(a!=b)        {            pre[b]=a;            cnt++;            sum+=node[i].len;            if(cnt==n-1)            {                return sum;            }        }    }    return -1;}int main(){    int T;    scanf("%d",&T);    while(T--)    {        input();        printf("%d\n",kruskal(0));    }    return 0;}
应用题目                      数据量                                                    耗时                      内存

模板            10w/mb                             O(mlogm)             O(n)

ZOJ3036稀疏图         50000个节点,2000条边                         120ms               1184k



POJ3522 差值最小生成树 枚举+kruskal

这道题的题意很明了。求最大边与最小边差值最小的生成树

首先,把所有的生成树都求出来是不可能的,所以,必须用别的方法。

在学习次小生成树的过程中,知道了一个最小生成树的性质, 一个图的最小生成树不一定是唯一的,但是组成这些最小生成树的各个边的权值一定都是一一对应相同的。不会出现这种一个树上有两个边权值a+b等于另外一颗树上两个边c+d,然后这两个树都是最小生成树的情况。  

对于本题来讲,上面那个性质就说明了一个图的最小生成树上的最小边的权值和最大边的权值是固定不变的。那么当使用克鲁斯卡尔算法时,第一次加入的必然是边权最小的边,由于克鲁斯卡尔算法的正确性已经得到验证过,那么此边的权值必然就是最小生成树的最小边权值了,当最小边的权值固定时,最小生成树的最大边的权值也“命中注定”是固定的,在最小边权值固定的情况下,其他的生成树的最大边必然也是大于等于最小生成树的最大边,否则就不满足我们上文提到的性质,这就是“当生成树的最小边确定时,最小生成树的最大边的权值是所有生成树中最小的”。从而,我们得到了一个本题的解法。  枚举最小边,然后求最小生成树,更新最优解。

//200K94MS#include <stdio.h>#include <stdlib.h>#include <iostream>#include <algorithm>#define N 110#define INF 0x7fffffffusing namespace std;struct ss{    int x,y;    int len;} city[N*N/2];int n,m,pre[N];int cmp(ss a,ss b){    return a.len<b.len;}void input(){    int i,x,y,len;    for(i=0; i<m; i++)scanf("%d%d%d",&city[i].x,&city[i].y,&city[i].len);    sort(city,city+m,cmp);//printf("---------------------\n");//for(i=0;i<m;i++)printf("%d %d %d\n",city[i].x,city[i].y,city[i].len);}int find( int x ){    if(x!=pre[x])pre[x]=find(pre[x]);    return pre[x];}int kruskal(int t){    int i,a,b,cnt=0;;    for(i=1; i<=n; i++)pre[i] = i;//printf("---\n");    for(i=t; i<m; i++)    {        a = find(city[i].x);        b = find(city[i].y);        if( a!= b)        {            pre[b]=a;            cnt++;            //     printf("** %d %d %d\n",city[i].x,city[i].y,city[i].len);            if(cnt==n-1)            {                //       printf("%d\n",city[i].len-city[t].len);                return city[i].len-city[t].len;            }        }    }    return -1;}int main(){    while(scanf("%d%d",&n,&m)&&(n+m))    {        input();        int i;        int ans=INF;        for(i=0; i<m; i++)        {            int t=kruskal(i);            if(t!=-1&&t<ans)ans=t;        }        if(ans!=INF)printf("%d\n",ans);        else printf("-1\n");    }    return 0;}

湖南师范大学第五届大学生计算机程序设计竞赛--G--修路11464--最小生成树的kruskal算法

题目链接:http://acm.hunnu.edu.cn/online/?action=problem&type=show&id=11464&courseid=132

修路Time Limit: 2000ms, Special Time Limit:5000ms, Memory Limit:32768KB Problem 11464 : No special judgementProblem description  在X国家,有n个城市,只有在城市才有加油站,现在国王决定修一些道路来使这n个城市链接起来,使得任意二个城市可以互相到达。由于经济危机,现在国王只有y元用来修路。由于只有在城市里才有加油站,所以当一条道路太长的时候,车辆由于油量不够,不同在这两个城市间互相到达。所以国王要求每条道路不能太长,即使得修的道路中最长的道路最短。现在问你最长的道路的长度。Input  包含多组数据,每组数据第一行为一个数n m 和y,表示n个城市和m条可以修的道路,y元的修路费2<=n<=10000 1<=m<=100000,1<=y<=100000。
接下来m行每行有四个整数u v c d 表示该道路链接城市u和v 修路的费用为c长度为d。
1<=u,v<=n   1<=d<=100000  1<=c<=10Output  对于每组数据,输出一个整数,表示修路中的最长的道路的长度,如果修的道路不能任意二个城市可以互相到达 则输出 -1。Sample Input
3 2 101 2 2 11 3 2 53 3 101 2 5 2 2 1 6 1 1 3 5 13 2 1001 2 1 11 2 2 2 
Sample Output
52-1

思路:题意为在满足费用y的情况下,将所有的城市连起来,并且使选择的所有道路中,最长的那一条最短,故可以枚举最长道路的长度,然后检验在这种长度内的道路能否符合国王的要求,找到一个最小值即为答案,其中枚举过程采用二分优化,检验过程用并查集,代码如下~。

代码如下:

参考:http://blog.csdn.net/angel_e/article/details/29576035

注意cmp中的cost的排序 用cost为权重做最小生成树

#include<stdio.h>#include<stdlib.h>#include<iostream>#include<algorithm>#define N 10010#define M 100010using namespace std;struct ss{    int x,y;    int len;    int c;} city[M];int n,m,y,pre[N];int cmp(ss a,ss b){    return a.c<b.c;}void input(){    int i,x,y,len;    for(i=0; i<m; i++)scanf("%d%d%d%d",&city[i].x,&city[i].y,&city[i].c,&city[i].len);    sort(city,city+m,cmp);}int find( int x ){    if(x!=pre[x])pre[x]=find(pre[x]);    return pre[x];}int kruskal(int len){    int i,a,b,cnt=0;    int cost=0;    for(i=1; i<=n; i++)pre[i] = i;    for(i=0; i<m; i++)    {        if(city[i].len>len)continue;        a = find(city[i].x);        b = find(city[i].y);        if( a!= b)        {            pre[b]=a;            cnt++;            cost+=city[i].c;            if(cost>y)break;            if(cnt==n-1)            {                return city[i].len;            }        }    }    return 0;}int main(){    while(~scanf("%d%d%d",&n,&m,&y))    {        input();        int i;        int ans=-1;        int l=1,r=1,mid;        for(i=0;i<m;i++)r=r>city[i].len?r:city[i].len;        if(!kruskal(r)){            printf("-1\n");            continue;        }        while(l<r)        {            mid=(l+r)/2;            if(kruskal(mid))r=mid;            else l=mid+1;        }        printf("%d\n",l);    }    return 0;}








0 1