Constructing Roads
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 9149 Accepted Submission(s): 3383
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.
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.
Prim算法
1.概览
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点
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被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 | C, G | A, B, E, F | D | |
下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。 | C, G | B, E, F | A, D | |
算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 | C | B, E, G | A, D, F | |
在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。E最近,因此将顶点E与相应边BE高亮表示。 | 无 | C, E, G | A, D, F, B | |
这里,可供选择的顶点只有C和G。C距E为5,G距E为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也已经连通了(这里上图的连通线用红色表示了)。
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\n",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 }
本小白觉得的 这个 跟 最短路有些像呢
来源:https://www.cnblogs.com/sxy-798013203/p/5288901.html