最小生成树 Prim、Kruscal算法 (以HDU 1863为例)

来源:互联网 发布:python基础语句 编辑:程序博客网 时间:2024/06/15 04:38

畅通工程

                                                                 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
                                                                                            Total Submission(s): 32680    Accepted Submission(s): 14407


Problem Description
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。
 

Input
测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N 
行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。
 

Output
对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。
 

Sample Input
3 31 2 11 3 22 3 41 32 3 20 100
 

Sample Output
3?
 

Source
浙大计算机研究生复试上机考试-2007年

典型的最小生成树算法,下面就来介绍一下最小生成树算法以及其代码实现。

最小生成树的性质:设G = (V,E)是连通带权图,U是V的真子集。如果(u,v)∈E,且u∈U,v∈V-U,且在所有这样的边中,

(u,v)的权c[u][v]最小,那么一定存在G的一棵最小生成树,(u,v)为其中一条边。

两种求解方式及其复杂度: Prim算法,邻接矩阵时间复杂度O(n^2),邻接表时间复杂度O(mlog2n);Kruskal算法时间复杂度O(mlogm)。n为点的个数,m为边的条数。

Prim算法

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来描述所得到的最小生成树。

具体实现:以任意点为顶点,找到此点所有可以到达的点,标记当前顶点,找到可到达的点最近的顶点标记,将此顶点可到达的顶点加入数组,用来再去找可以路径最短的,直到标记所有的点为止,最后输出sum。

代码模板:(邻接矩阵)

#include<iostream> #include<algorithm>#include<cstring>#include<cmath>#include<queue>#include<cstdio>#include<set>#define ll long long#define mset(a,x) memset(a,x,sizeof(a))using namespace std;const double PI=acos(-1);const int inf=0x3f3f3f3f;const double esp=1e-6;const int maxn=105;const int mod=1e9+7;int dir[4][2]={0,1,1,0,0,-1,-1,0};ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}ll lcm(ll a,ll b){return a/gcd(a,b)*b;}ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}int map[maxn][maxn],visit[maxn],dis[maxn],n,m;   //n代表边数,m代表点数,map存路径,dis存当前点到下一点距离,visit标记 void init(){int i;mset(visit,0);mset(map,inf);for(i=0;i<=maxn;i++)map[i][i]=0;}void prim(int s){int i,j,k;for(i=1;i<=m;i++)                   //赋初值 dis[i]=map[s][i];visit[s]=1;for(i=2;i<=m;i++){int minn=inf;k=0;for(j=1;j<=m;j++){if(!visit[j]&&minn>dis[j]){minn=dis[j];k=j;}}visit[k]=1;for(j=1;j<=m;j++)                //更新 {if(!visit[j]&&dis[j]>map[k][j])dis[j]=map[k][j];}}}int main(){int i,j,k,x,y,z;while(~scanf("%d%d",&n,&m)){if(n==0)break;init();for(i=0;i<n;i++){cin>>x>>y>>z;if(map[x][y]>z){map[x][y]=map[y][x]=z;}}prim(1);int flag=1;for(i=1;i<=m;i++)if(dis[i]==inf)flag=0;if(flag){int ans=0;for(i=1;i<=m;i++)ans+=dis[i];printf("%d\n",ans);}elsecout<<"?"<<endl;}return 0;}

邻接表

#include <iostream>#include <queue>#include <algorithm> #include <string.h>#include <math.h>using namespace std;const int maxx=200;const int maxn=1e4+10;const int  inf=0x3f3f3f3f;int sum,ans,m,n,q;struct point {int next;int to;int val;bool operator < (const point &a) const{return val>a.val; } };point pt[2*maxn];bool vis[maxx];int dis[maxx],head[2*maxn];void add(int u,int v,int val){pt[q].next=head[u];pt[q].val=val;pt[q].to=v;head[u]=q++;}void prim(int st){priority_queue<point>q;point t1,t2;ans=1,sum=0;memset(vis,false,sizeof(vis));memset(dis,inf,sizeof(dis));for (int i=head[st];i!=-1;i=pt[i].next){int v=pt[i].to;if (pt[i].val<dis[v]){dis[v]=pt[i].val;t1.to=v;t1.val=dis[v];q.push(t1);}}vis[st]=true;while (!q.empty()){t1=q.top();q.pop();int v=t1.to;if (vis[v])continue;vis[v]=true;sum+=dis[v];ans++;for (int i=head[v];i!=-1;i=pt[i].next){int u=pt[i].to;if (!vis[pt[i].to]&&pt[i].val<dis[u]){dis[u]=pt[i].val;t2.to=pt[i].to;t2.val=dis[u];q.push(t2);}}}}int main(){int u,v,val,st; while(~scanf("%d%d",&n,&m)) { if (n==0) break; q=1; memset(head,-1,sizeof(head)); for (int i=0;i<n;i++) { scanf("%d%d%d",&u,&v,&val); add(u,v,val); add(v,u,val); st=u;}prim(st);if (ans==m)printf("%d\n",sum);elseprintf("?\n"); } return 0; } 

Kruscal算法

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

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

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

4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中

                if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中

                                         添加这条边到图Graphnew

具体实现:将边的权值经行排序,从最小的边开始添加,每次都加入最小的边,运用并查集,最后得到结果。

代码模板:

#include<iostream> #include<algorithm>#include<cstring>#include<cmath>#include<queue>#include<cstdio>#include<set>#define ll long long#define mset(a,x) memset(a,x,sizeof(a))using namespace std;const double PI=acos(-1);const int inf=0x3f3f3f3f;const double esp=1e-6;const int maxn=105;const int mod=1e9+7;int dir[4][2]={0,1,1,0,0,-1,-1,0};ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}ll lcm(ll a,ll b){return a/gcd(a,b)*b;}ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}struct edge{int s,t;int len;}p[maxn*maxn];int pre[1005];int n,m;int cmp(edge a,edge b){return a.len<b.len;}int find(int x){if(pre[x]==x)return x;return pre[x]=find(pre[x]);}int main(){int i,j,k,x,y,z;while(~scanf("%d%d",&n,&m)&&n){for(i=1;i<=m;i++)pre[i]=i;for(i=0;i<n;i++){cin>>x>>y>>z;p[i].s=x;p[i].t=y;p[i].len=z;}sort(p,p+n,cmp);int ans=0,count=0;for(i=0;i<n&&count<m-1;i++){int fx=find(p[i].s);int fy=find(p[i].t);if(fx!=fy){pre[fx]=fy;                  ans+=p[i].len;                count++;}}if(count==m-1)cout<<ans<<endl;elsecout<<"?"<<endl;}return 0;}


原创粉丝点击