最小生成树算法(prim 克鲁斯卡尔(并差集))

来源:互联网 发布:软件项目开发报告 编辑:程序博客网 时间:2024/04/30 14:40

今天复习dijsktra的时候顺便学习了一下求最小生成树的几个算法。求最小生成树有多种算法。其中多种算法运用的是最小生成树的MST性质:假设G=(V,E)是一个联通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则一定存在一颗包含边(u,v)的树。这里先说说运用这个性质的prim算法。

prim算法的算法思想是这样的:假设G=(V,E)是联通网,T=(U,TE)为预构成的最小生成树,TE是最小生成树的边的集合,U是顶点集合。从TE=空集和U={u0},u0属于V开始,重复的执行这样的操作:在所有的u属于U,v属于V-U的边(u,v)属于E中,找一条代价最小的(u,v),将其压入到集合TE中,同时v假如U,直至U==V,此时TE中必有n-1条边,则T=(V,TE)为N的最小生成树。

Prim实现可以用普通的二维数组,快一点的话用优先队列+二维数组。优先队列实现的话用一个结构体存点的之和点到前一点的距离。运用优先队列的特性每次都返回与上一点权值最小的且没有找过的点。然后借助lowcost[ ]数组进行更新优先队列中的值,知道所有的都找完。

算法可以用线性表实现:这里看一道例题:

nyoj38布线问题   标准的模板题,代码:

#include <stdio.h>#include <string.h>#define inf 9999999#define MAX 510int map[MAX][MAX];       //存图的值int vis[MAX],lowcost[MAX];          //标记数组和更新权值的数组int T,v,e;int Prim(int x){    memset(vis,0,sizeof(vis));    memset(lowcost,-1,sizeof(lowcost));    int ans=0;    for(int i=1;i<=v;i++)        lowcost[i]=map[x][i];    lowcost[x]=0;    vis[x]=1;    int k=x;    for(int i=1;i<v;i++)    {        int Smin=inf;        for(int j=1;j<=v;j++)        //找距离最小的点的权值,在优先队列的实现中这一步由优先队列的特性自己实现了        {            if(!vis[j]&&lowcost[j]&&lowcost[j]<Smin){                k=j;Smin=lowcost[j];            }        }        ans+=lowcost[k];        vis[k]=1;        for(int j=1;j<=v;j++)      //更行lowcost数组,继续下次查找        {            if(!vis[j]&&map[k][j]&&map[k][j]<lowcost[j])                lowcost[j]=map[k][j];        }    }    return ans;}int main(){    scanf("%d",&T);    while(T--)    {        int a,b,c,d;        memset(map,0,sizeof(map));        scanf("%d%d",&v,&e);        for(int i=0;i<e;i++){            scanf("%d%d%d",&a,&b,&c);            if(map[a][b]){                if(map[a][b]>c)                {                    map[a][b]=map[b][a]=c;                }            }            else                map[a][b]=map[b][a]=c;        }        int Min=inf;        for(int i=1;i<=v;i++){            scanf("%d",&d);            if(d<Min)                Min=d;        }        printf("%d\n",Min+Prim(1));    }    return 0;}

下面看看优先队列的实现,这道题用优先队列和没有用运行时间差别不大,应该说是优先队列快点、看代码:

#include <stdio.h>#include <string.h>#include <iostream>#include <queue>#define inf 9999999#define MAX 510using namespace std;int map[MAX][MAX];int vis[MAX],lowcost[MAX];int T,v,e;struct Info    //优先队列,每次都返回的是边的长度最小的点{    int ve;    //点    int dis;    //边的长度    bool operator < (const Info &a) const   //从小到大返回边的长度值    {        return a.dis<dis;    }};int Prim(int start){    int ans=0;    memset(vis,0,sizeof(vis));    memset(lowcost,inf,sizeof(lowcost));    Info pre, cur;    priority_queue<Info> Q;    lowcost[start] = 0;    pre.dis = 0;    pre.ve = start;    Q.push( pre );    while( !Q.empty() )    {        pre = Q.top();        Q.pop();        if( vis[pre.ve] )         //注意这里,找过的点就不用再找了            continue;        ans += pre.dis;        vis[pre.ve] = 1;        for( int i = 1; i <= v; i++ )        {            if( !vis[i] &&  map[pre.ve][i] < lowcost[i] )      //找最小的入队            {                lowcost[i] = map[pre.ve][i];                cur.dis = lowcost[i];                cur.ve = i;                Q.push( cur );            }        }    }    return ans;}int main(){    scanf("%d",&T);    while(T--)    {        int a,b,c,d;        memset(map,inf,sizeof(map));        scanf("%d%d",&v,&e);        for(int i=0;i<e;i++){            scanf("%d%d%d",&a,&b,&c);            if(map[a][b]){                if(map[a][b]>c)                {                    map[a][b]=map[b][a]=c;                }            }            else                map[a][b]=map[b][a]=c;        }        int Min=inf;        for(int i=1;i<=v;i++){            scanf("%d",&d);            if(d<Min)                Min=d;            //printf("%d",d);        }        printf("%d\n",Min+Prim(1));    }    return 0;}

        又学习了求最小生成数的另一种算法,克鲁斯卡尔算法,首先这个算法的思想:首先令一个连通图G=(V,E)中的每一个顶点都自成一个连通分量,最小生成树的初始状态为只有n个顶点没有边的非连通图T=(V,{ }),在E中每次选择代价最小的边,如果这个边依附的顶点分别在T中的不同的连通分量上,就将此边加入到T中,否则,舍去这个边而选择下一条代价最小的边,就这样,直到T中所有的顶点构成一个连通分量即为所求的最小生成树。

       它和prim有什么不同呢,prim是从任意一个点开始每次找与它相连的最小的边,直到形成一个n-1条边形成最小生成树,而 kruskal 不是从顶点开始的,它每次先找一个最小的边,然后判断这条边依附的顶点是否在两个不同的连通分量上,是的话就加入,直到所有的点构成一个连通分量。

       但是这里最核心的一步就是判断一条边依附的两个顶点是否在不同的两个连通分量上,这里就要用到另一个算法,并查集,所以我学习可 kruskal 的思想之后就紧接着学习了一下并差集。并查集也是一个很常用的一个基础算法,很有必要学习一个。

可以看这个链接:讲的很清晰明了。下面先看一道并查集模板题目:1160 - X-Plosives   代码:

#include <cstdio>using namespace std;const int maxn = 100010;int pa[maxn];int find(int x){    //并查集的查找操作,带路径压缩.可以自己模拟一下这个过程,很好用的一个处理。    return pa[x] != x ? pa[x]=find(pa[x]) : x;}int main(){int x, y;while(~scanf("%d", &x)){    for(int i = 0; i < 100001; i++) pa[i] = i;    //并差集初始化int ans = 0;while(x != -1){scanf("%d", &y);x = find(x), y = find(y);if(x != y) pa[x] = y;   //合并else ans++;scanf("%d", &x);}printf("%d\n", ans);}return 0;}

根据并查集的这个特性就可以很好的写出kruskal算法,这里给出一道模板题目:

10369 - Arctic Network   没有看懂题目,但是看别人翻译就是求最小生成树的第s-p小权值的边,这里对边的权值排序的时候运用了一个间接排序,看不懂的可以自己输出来看一下,确实精妙。其实当初我想的是不是可以用优先队列来实现每次输出最小的边,没想到可以这样,排序连结构体都省了,看来还要好好的学习啊,不废话了,代码:

#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>using namespace std;#define N 500#define M 200000int n, m, t, dot[N][2];int v[M], u[M], r[M], p[M];double w[M];void init(){    int dx, dy;    scanf("%d%d",&t,&n);    for(int i = 0; i < n; i++)        scanf("%d%d",&dot[i][0],&dot[i][1]);    m = 0;    for(int i = 0; i < n; i++)    for(int j = i+1; j < n; j++)    {        dx = dot[i][0]-dot[j][0]; dy = dot[i][1]-dot[j][1];        w[m] = sqrt(dx*dx+dy*dy);        u[m] = i;        v[m] = j;        m++;    }}int cmp(const int i, const int j) { return w[i]<w[j]; }int find(int x) { return p[x]==x?x:p[x] = find(p[x]); }double kruskal(){    int cnt = 0;    for(int i = 0; i < n; i++) p[i] = i;    for(int i = 0; i < m; i++) r[i] = i;    sort(r,r+m,cmp);    for(int i = 0; i < m; i++)    {        int e = r[i];        int x = find(u[e]); int y = find(v[e]);        if(x!=y)        {            if(++cnt==n-t)  return w[e];            p[x] = y;        }    }    return 0.0;}int main (){    int cas;    scanf("%d",&cas);    while(cas--)    {        init();        printf("%.2lf\n",kruskal());    }    return 0;}



      


                                                 Picnic Planning

Time Limit: 5000MS Memory Limit: 10000KTotal Submissions: 8021 Accepted: 2822

Description

The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible ability to cram an unlimited number of themselves into even the smallest vehicle. During the off-season, the brothers like to get together for an Annual Contortionists Meeting at a local park. However, the brothers are not only tight with regard to cramped quarters, but with money as well, so they try to find the way to get everyone to the party which minimizes the number of miles put on everyone's cars (thus saving gas, wear and tear, etc.). To this end they are willing to cram themselves into as few cars as necessary to minimize the total number of miles put on all their cars together. This often results in many brothers driving to one brother's house, leaving all but one car there and piling into the remaining one. There is a constraint at the park, however: the parking lot at the picnic site can only hold a limited number of cars, so that must be factored into the overall miserly calculation. Also, due to an entrance fee to the park, once any brother's car arrives at the park it is there to stay; he will not drop off his passengers and then leave to pick up other brothers. Now for your average circus clan, solving this problem is a challenge, so it is left to you to write a program to solve their milage minimization problem.

Input

Input will consist of one problem instance. The first line will contain a single integer n indicating the number of highway connections between brothers or between brothers and the park. The next n lines will contain one connection per line, of the form name1 name2 dist, where name1 and name2 are either the names of two brothers or the word Park and a brother's name (in either order), and dist is the integer distance between them. These roads will all be 2-way roads, and dist will always be positive.The maximum number of brothers will be 20 and the maximumlength of any name will be 10 characters.Following these n lines will be one final line containing an integer s which specifies the number of cars which can fit in the parking lot of the picnic site. You may assume that there is a path from every brother's house to the park and that a solution exists for each problem instance.

Output

Output should consist of one line of the form 
Total miles driven: xxx 
where xxx is the total number of miles driven by all the brothers' cars.

Sample Input

10Alphonzo Bernardo 32Alphonzo Park 57Alphonzo Eduardo 43Bernardo Park 19Bernardo Clemenzi 82Clemenzi Park 65Clemenzi Herb 90Clemenzi Eduardo 109Park Herb 24Herb Eduardo 793

Sample Output

Total miles driven: 183

Source

prim算法。。。未完待续,写不下去了

#include <iostream>#include<string.h>using namespace std;const int mmax=21;const int max=9999999;struct Node{    int sv,ev;    int w; //边的权值};int n,s,nv,ans,flag;   //nv顶点个数,n边的个数char vdes[mmax][12];int vis[mmax],map[mmax][mmax];Node mst[mmax-1];int Verind(char* name);int Mstbyprim();void Maxweight(int mv,int sv,int ev,int& maxw,int& ind);void Solve();int main(){    char namel[12],name2[12];    int dist,i,j;    for(i=0;i<mmax;i++)    {        for(j=0;j<mmax;j++)        map[i][j]=max;    }    nv=0;    cin>>n;    strcpy(vdes[nv],"Park");nv++:    for(i=0;i<n;i++)    {        cin>>name1>>name2>>dist;        int ind1=Verind(name1),ind2=Verind2(name2);        map[ind1][ind2]=map[ind2][ind1]=dist;    }    cin>>s;//最小生成树的顶点的最大度数    Solve();    cout<<"Total miles driven: "<<ans<<endl;    return 0;}int Verind(char* name){    int ind=0;    while(ind<nv && strcmp(vdes[ind],name)!=0)    ind++;    if(ind==nv)    {        strcmp(vdes[nv],name);nv++;    }}int Mstbyprim(){    int mstws=0;    int i,j,k;    for(i=0;i<nv-1;i++)    {        mst[i].sv=1;mst[i].ev=i+1;mst[i].w=map[1][i+1];    }    for(k=2;k<nv;k++)    {        int minw=mst[k-1].w,ind=k-1;        for(j=k;j<nv-1;j++)        {            if(mst[j].w<minw)            minw=mst[j].w;ind=j;        }        mstws+=minw;        Node tmp=mst[k-1];mst[k-1]=mst[ind];mst[ind]=temp;        j=mst[k-1].ev;for(i=k;i<nv-1;i++)        {            int v=mst[i].ev,w=map[j][v];            if(w<mst[i].w)            mst[i].w=w;mst[i].sv=j;        }    }    return mstws;}void Maxweight(int mv,int sv,int ev,int& maxw,int& ind){    if(ev>mv)    {    }}


原创粉丝点击