今天复习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 510 int 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 510 using 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 200000 int 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; }
Time Limit: 5000MS | Memory Limit: 10000K | |
Total Submissions: 8021 | Accepted: 2822 |
Description
Input
Output
Sample Input
10 Alphonzo Bernardo 32 Alphonzo Park 57 Alphonzo Eduardo 43 Bernardo Park 19 Bernardo Clemenzi 82 Clemenzi Park 65 Clemenzi Herb 90 Clemenzi Eduardo 109 Park Herb 24 Herb Eduardo 79 3
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) { } }