次小生成树
来源:互联网 发布:手机淘宝怎么小二介入 编辑:程序博客网 时间:2024/05/17 20:26
最小生成树与次小生成树
我们都知道要求最小生成树有两种方法可求,分别是prim算法和kruskal算法
1.prim算法
设图G =(V,E),其生成树的顶点集合为U。
①、把v0放入U。
②、在所有u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树。
③、把②找到的边的v加入U集合。如果U集合已有n个元素,则结束,否则继续执行②。
2.kruskal算法首先将所有边按边权排序,然后按照边权从小到大依次处理.这里要用到并查集的思想,假设已有点集U,现在正在处理边i->j,如果i,j已在
U中则处理下一条边,否则将i,j加入并查集中.继续处理下一条边,直到有n-1条边为止.
3 次小生成树
次小生成树可由最小生成树换一条边得到
算法:
1)先用prim求出最小生成树T,在prim的同时,用一个矩阵max[u][v]记录在树中连接u-v的路径中权值最大的边.
2)枚举所有不在T中的边u-v,加入边u-v,删除权值为max[u][v]的边,不断枚举找到次小生成树.
下面给出次小生成树的模板
#include<iostream>#include<cstdio>#include<cstring>using namespace std;const int INF=0x3f3f3f3f;int g[110][110],dist[110],mmax[110][110]; ///g保存地图 dist保存从起点到其余各点的距离 maxn保存从i到j的最大边权值int pre[110]; ///pre保存j的离它最近的是哪个点bool mark[110]; ///相当于vis 用来标记该点是否已经用过bool connect[110][110]; ///保存i-j的那条边是否加入了最小生成树 false 加入 true 没有int mst,mint; ///mst保存最小生成树的权值和int n,m;int prim(){ int res=0,fa,p,min,i,j; memset(mmax,0,sizeof(mmax)); for(i=1;i<=n;i++) { dist[i]=g[1][i]; pre[i]=1; mark[i]=false; } dist[1]=0; mark[1]=true; for(i=1;i<n;i++) { p=-1;min=INF; for(j=1;j<=n;j++) { if(!mark[j]&&dist[j]<min) { p=j; min=dist[j]; } } if(p==-1) return res; mark[p]=true; res+=dist[p]; fa=pre[p]; ///找到离p最近的点 connect[fa][p]=false; connect[p][fa]=false; mmax[fa][p]=min; ///遍历所有的点 求其余点到p的最大权值 for(j=1;j<=n;j++) mmax[j][p]=(mmax[fa][p]>mmax[j][fa])?mmax[fa][p]:mmax[j][fa]; for(j=1;j<=n;j++) { if(!mark[j]&&dist[j]>g[p][j]) { dist[j]=g[p][j]; pre[j]=p; } } } return res;}int main(){ int tc; scanf("%d",&tc); while(tc--) { scanf("%d %d",&n,&m); memset(g,INF,sizeof(g)); memset(connect,false,sizeof(connect)); while(m--) { int u,v,c; scanf("%d %d %d",&u,&v,&c); g[u][v]=c; g[v][u]=c; connect[u][v]=true; connect[v][u]=true; } mst=prim(); int i,j; bool flag=false; for(i=1;i<=n;i++) for(j=1;j<=n;j++) { ///如果i-j这条边加入了最小生成树 或者i-j这条路不通 continue if(connect[i][j]==false||g[i][j]==INF) continue; ///如果加入的边和删除的边的大小是一样的 说明次小生成树的权值和等于最小生成树的权值和 ///也就是说最小生成树不唯一 if(g[i][j]==mmax[i][j]) { flag=true; break; } } if(flag) printf("Not Unique!\n"); else printf("%d\n",mst); } return 0;}
下面来一道题练练手
http://acm.hdu.edu.cn/showproblem.php?pid=4081
题意:
题目大意:
有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点。秦始皇希望这所有n-1条路长度之和最短。然后徐福突然有冒出来,说是他有魔法,可以不用人力、财力就变出其中任意一条路出来。
秦始皇希望徐福能把要修的n-1条路中最长的那条变出来,但是徐福希望能把要求的人力数量最多的那条变出来。对于每条路所需要的人力,是指这条路连接的两个城市的人数之和。
最终,秦始皇给出了一个公式,A/B,A是指要徐福用魔法变出的那条路所需人力, B是指除了徐福变出来的那条之外的所有n-2条路径长度之和,选使得A/B值最大的那条。
分析与总结
为了使的A/B值最大,首先是需要是B尽量要小,所以可先求出n个城市的最小生成树。然后,就是决定要选择那一条用徐福的魔法来变。
因此,可以枚举每一条边,假设最小生成树的值是MinMST, 而枚举的那条边长度是w[i][j], 如果这一条边已经是属于最小生成树上的,那么最终式子的值是A/(MinMST-w[i][j])。如果这一条不属于最小生成树上的, 那么添加上这条边,就会有n条边,那么就会使得有了一个环,为了使得它还是一个生成树,就要删掉环上的一棵树。 为了让生成树尽量少,那么就要删掉除了加入的那条边以外,权值最大的那条路径。 假设删除的那个边的权值是path[i][j], 那么就是A/(MinMST-path[i][j]).
解这题的关键也在于怎样求出次小生成树。
AC代码:
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#define INF 0x3f3f3f3f#define MAX 1010using namespace std;double Map[MAX][MAX], dis[MAX], maxn[MAX][MAX]; ///Map数组保存地图 dis保存从起点到其他点的距离 maxn保存i到j的最大权值int pre[MAX], vis[MAX];bool used[MAX][MAX];int n;struct Edge{ double x, y, p;}edge[MAX];double cal(Edge a, Edge b){ return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y)*(a.y - b.y));}double prim(){ double ret = 0, minn; int father; memset(vis, 0, sizeof(vis)); memset(maxn, 0, sizeof(maxn)); memset(used, 0, sizeof(used)); for(int i = 1; i <= n; i++) { dis[i] = Map[1][i]; pre[i] = 1; } vis[1] = 1; int k; for(int i = 1; i < n; i++) { minn = INF; for(int j = 1; j <= n; j++) { if(!vis[j]&&dis[j] < minn) { minn = dis[j]; k = j; } } if(minn == INF) return -1; vis[k] = 1; ret+=minn; father = pre[k]; used[father][k] = used[k][father] = 1; maxn[father][k] = minn; for(int j = 1; j <= n; j++) { if(vis[j]&&j!=k) ///不明白为什么j为什么不能等于k maxn[j][k] = maxn[k][j] = max(maxn[j][father], maxn[father][k]); } for(int j = 1; j <= n; j++) { if(!vis[j]&&dis[j] > Map[k][j]) { dis[j] = Map[k][j]; pre[j] = k; } } } return ret;}int main(){ int t; scanf("%d",&t); while(t--) { scanf("%d",&n); for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { Map[i][j] = INF; } Map[i][i] = 0; } for(int i = 1; i <= n; i++) { scanf("%lf%lf%lf",&edge[i].x, &edge[i].y, &edge[i].p); } for(int i = 1; i <= n; i++) { for(int j = i + 1; j <= n; j++) { Map[i][j] = Map[j][i] = cal(edge[i], edge[j]); } } double ans = prim(); double ans1 = -1; for(int i = 1; i<= n; i++) { for(int j = 1; j <= n; j++) { if(i != j) { if(used[i][j]) ans1 = max(ans1, (edge[i].p + edge[j].p)/(ans - Map[i][j])); else ans1 = max(ans1, (edge[i].p + edge[j].p)/(ans - maxn[i][j])); } } } printf("%.2lf\n",ans1); } return 0;}
但是一直有一个疑问,就是不明白为什么遍历各个点到k的最大边权值的时候,j不能等于k,有明白的大神们,请留言啊。
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 次小生成树
- 记,初次开通个人博客!
- 前端如何解析ajax的list对象
- Java内存分配
- Windows下安装配置MongoDB
- Microsoft Access、MySQL 以及 SQL Server 所使用的数据类型和范围。
- 次小生成树
- wine 64源码安装遇到的问题
- 详解el表达式
- Codevs 1173 最优贸易
- tensorflow初学之notMNIST
- tangniaobing .....
- 用NSAttributedString实现简单的图文混排-----(确实几个需要注意的地方都注意到了就好了。)
- matlab循环结构
- js prototype 和call