最小生成树 浅浅的花香味﹌ 2021-12-22 01:57 301阅读 0赞 ## 最小生成树 ## ### 什么是最小生成树 ### * 给定一张无向带权图G=(V,E,W),从中**挑选出|V|-1条边**,使得V中任意两点都有直接或者间接的路径 * 显然,最后得到的子图是一棵树,如果这棵树是所有树中边权和最小的,那么它就是图G的最小生成树 * 一幅图中的最小生成树可能不止一个 ### 克鲁斯卡尔算法 ### * 算法流程 * 把边按照从小到大排序等待挑选进集合S * 尝试加入当前给定的边,如果加入这个边使得集合S中不存在环,则加入该边,否则跳过 * 当遍历完所有边或者S集合中已经包含所有顶点时算法结束 * 源码 HDU1223 #include<iostream> #include<queue> #include<list> #include<vector> #include<cstring> #include<set> #include<stack> #include<map> #include<cmath> #include<algorithm> #include<string> #include<stdio.h> using namespace std; typedef long long ll; #define MS(x,i) memset(x,i,sizeof(x)) #define rep(i,s,e) for(int i=s; i<=e; i++) #define sc(a) scanf("%d",&a) #define scl(a) scanf("%lld",&a) #define sc2(a,b) scanf("%d %d", &a, &b) #define debug printf("debug......\n"); #define pfd(x) printf("%d\n",x) #define pfl(x) printf("%lld\n",x) const double eps=1e-8; const double PI = acos(-1.0); const int inf = 0x3f3f3f3f; const ll INF = 0x7fffffff; const int maxn = 2e2+10; const int M = 1e4+10; int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0 , 0}; int n;//边数 int m;//顶点数 int fa[maxn];//根节点 //把边按照从小到大排序 struct node{ int u,v,w; }edge[M]; bool cmp(node a, node b){ return a.w < b.w; } //并查集基本操作 void init(){ rep(i,1,n){ fa[i] = i; } } int find(int x){ if(x != fa[x]){ return fa[x] = find(fa[x]); } return fa[x]; } void merge(int x, int y){ int fx = find(x); int fy = find(y); if(fx != fy){ fa[fx] = fa[fy]; } } int main(){ int x,y,w; ios::sync_with_stdio(false); while(cin>>n){ if(n == 0) break; m = (n-1)*n/2; int sum = 0; init(); rep(i , 1, m){ cin>>x>>y>>w; edge[i].u = x; edge[i].v = y; edge[i].w = w; } sort(edge+1, edge+1+m,cmp);//把边从小到大 //从小到大筛边 遇到 环则跳过 rep(i,1,m){ node cur = edge[i]; int u = cur.u; int v = cur.v; int w = cur.w; //如果u,v处于同一个集合,在加入uv边则必形成环 if(find(u) != find(v)){ sum += w; merge(u,v); } } cout<<sum<<endl; } return 0; } ### ### ### Prim算法 ### * 算法流程 * 从任意源点s出发,s加入集合S * 对于S集合中的所有点,找出这些点所连接的所有边,选出一个最短边uv,其中u属于集合S,v不属于集合S,把v加进集合S * 当集合S中包含所有顶点,算法结束 * 源码 HDU1223 #include<iostream> #include<queue> #include<list> #include<vector> #include<cstring> #include<set> #include<stack> #include<map> #include<cmath> #include<algorithm> #include<string> #include<stdio.h> using namespace std; typedef long long ll; #define MS(x,i) memset(x,i,sizeof(x)) #define rep(i,s,e) for(int i=s; i<=e; i++) #define sc(a) scanf("%d",&a) #define scl(a) scanf("%lld",&a) #define sc2(a,b) scanf("%d %d", &a, &b) #define debug printf("debug......\n"); #define pfd(x) printf("%d\n",x) #define pfl(x) printf("%lld\n",x) const double eps=1e-8; const double PI = acos(-1.0); const int inf = 0x3f3f3f3f; const ll INF = 0x7fffffff; const int maxn = 2e2+10; int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0 , 0}; int dis[maxn];//记录每个顶点到S集合的所有边的长度 int vis[maxn];//点是否在S集合 int G[maxn][maxn];//存图 int x,y,w; int sum; int n,m; void Prim(int s){ MS(vis , 0); vis[s] = 1; //s进入S集合,初始化一下dis rep(i,1,n){ dis[i] = G[i][s]; } //还需要选n-1个点 rep(i , 1 , n-1){ int minx=inf; int index; //从dis中找出一个最小的 rep(j,1,n){ if(!vis[j] && dis[j] < minx){ minx = dis[j]; index = j; } } //这个顶点被加入S集合 因为这个顶点到S集合的边最小 vis[index] = 1; sum += minx;//最小生成树总长加上该边的值 //由于这个顶点的加入,使得余下V-S顶点到S集合的距离发生了变化 rep(j , 1, n){ if(!vis[j] && G[index][j] < dis[j]){ dis[j] = G[index][j]; } } } } int main(){ //while(cin>>n>>m){ while(cin>>n){ if(n == 0) break; rep(i,1,n) rep(j,1,n) if(i==j) G[i][j] = 0; else G[i][j] = inf; sum = 0; rep(i,1,n*(n-1)/2){ cin>>x>>y>>w; G[x][y] = w; G[y][x] = w; } Prim(1); cout<<sum<<endl; } return 0; } ### 算法正确性证明 ### * Prim算法 为什么每次选取最短边的策略是正确的? * 用归纳法证明,首先如果只有一个顶点,显然成立 n=1 * 假设n=k成立,即S集合中已经有k个顶点,并且选择的边都是最小生成树的边,那么当n=k+1时,如果选择的边e'不是最短边e,则最后形成的生成树中,我们可以加入e,则必然形成一个环,我们把e'去掉则会得到另外一棵生成树,显然这个生成树的边权和更小。即n=k+1也是成立的。 * Kruscal算法 * 从Prim算法我们可以得出结论:“如果某个连通图属于最小生成树,那么所有从外部连接到该连通图的边中的一条最短的边必然属于最小生成树” * 当最小生成树被拆分成彼此独立的若干个连通分量的时候,所有能够连接任意两个连通分量的边中的一条最短边必然属于最小生成树 ### 堆优化的prim算法 ### * prim算法核心 void Prim(int s){ MS(vis , 0); vis[s] = 1; //s进入S集合,初始化一下dis rep(i,1,n){ dis[i] = G[i][s]; } //还需要选n-1个点 rep(i , 1 , n-1){ int minx=inf; int index; //*********优化点A*********** //从dis中找出一个最小的 rep(j,1,n){ if(!vis[j] && dis[j] < minx){ minx = dis[j]; index = j; } } //这个顶点被加入S集合 因为这个顶点到S集合的边最小 vis[index] = 1; sum += minx;//最小生成树总长加上该边的值 //*********优化点B*********** //由于这个顶点的加入,使得余下V-S顶点到S集合的距离发生了变化 rep(j , 1, n){ if(!vis[j] && G[index][j] < dis[j]){ dis[j] = G[index][j]; } } } } * 优化点A * 每次从1到n扫描显然比较慢,可以把待选边加入一个优先队列,每次找最小的边的时候就取出队首 * 优化点B * 很显然不需要更新所有的点到S集合的距离,只需要对新加入 S集合的点所引出的到达点进行距离更新即可(也就是把这些边加入优先队列) * 源码 HDU1223 洛谷P3366 #include<iostream> #include<queue> #include<list> #include<vector> #include<cstring> #include<set> #include<stack> #include<map> #include<cmath> #include<algorithm> #include<string> #include<stdio.h> using namespace std; typedef long long ll; #define MS(x,i) memset(x,i,sizeof(x)) #define rep(i,s,e) for(int i=s; i<=e; i++) #define sc(a) scanf("%d",&a) #define scl(a) scanf("%lld",&a) #define sc2(a,b) scanf("%d %d", &a, &b) #define debug printf("debug......\n"); #define pfd(x) printf("%d\n",x) #define pfl(x) printf("%lld\n",x) const double eps=1e-8; const double PI = acos(-1.0); const int inf = 0x3f3f3f3f; const ll INF = 0x7fffffff; const int maxn = 2e2+10; const int M = 1e4+10; int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0 , 0}; int n,m; int cnt; int head[maxn]; struct node{ int v; int w; int nxt; node(){} node(int a, int b){v=a;w=b;} }edge[2*M];//存图 bool operator <(node a, node b){ return a.w > b.w; } void addEdge(int u, int v, int w){ edge[cnt].v = v; edge[cnt].w = w; edge[cnt].nxt = head[u]; head[u] = cnt++; } int sum; bool vis[maxn];//表示i是否被加进集合S //队列元素 struct ed{ int u,v,w; }; bool in[2*M];//边i是否在队列中 void prim(int s){ sum = 0; priority_queue<node> q; MS(vis , 0); MS(in , 0); vis[s] = 1; for(int i=head[s]; i; i=edge[i].nxt){ int v = edge[i].v; int w = edge[i].w; q.push(node(v , w)); in[i] = 1; } while(!q.empty()){ node tp = q.top(); int v = tp.v; int w = tp.w; q.pop(); if(vis[v]){ cout<<"顶点 "<<v<<" 已经在集合S中"<<endl; continue; } vis[v] = 1;//顶点v被取出 cout<<"顶点 "<<v<<" 加入集合S中"<<endl; sum += w; for(int i=head[v]; i; i=edge[i].nxt){ int to = edge[i].v; int tw = edge[i].w; //如果这个顶点已经在S集合 或者 这条边已经在队列中就不加了 if(!vis[to] && !in[i]) q.push(node(to , tw)); } } } int main(){ while(sc(n)!=EOF){ m = (n-1)*n/2; int u,v,w; MS(head , 0); rep(i,1,m){ sc(u); sc(v); sc(w); addEdge(u , v, w); addEdge(v, u, w); } prim(1); /*rep(i,1,n) if(!vis[i]){ printf("orz\n"); continue; }*/ pfd(sum); } return 0; } 转载于:https://www.cnblogs.com/czsharecode/p/10722106.html
相关 最小生成树 最小生成树定义: 在一给定的无向图 G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即![(u, v)\\in E][u_ v_in E]),而 末蓝、/ 2022年09月20日 15:18/ 0 赞/ 181 阅读
相关 生成树相关问题(最小生成树变形,次小生成树,最小度限度生成树,极差最小生成树) 生成树相关问题(最小生成树变形,次小生成树,最小度限度生成树,极差最小生成树) 视频:[https://www.bilibili.com/video/BV1G64y187ke Bertha 。/ 2022年09月12日 05:52/ 0 赞/ 185 阅读
相关 最小生成树 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树在n个顶点的情形下,有n-1条边。生成树是对连 忘是亡心i/ 2022年08月03日 05:28/ 0 赞/ 203 阅读
相关 最小生成树 邻接矩阵建图+prim朴素[算法][Link 1] 代码通过HDU1102 \[cpp\] [view plain][] [copy][view plain] 1. 超、凢脫俗/ 2022年06月09日 04:27/ 0 赞/ 50 阅读
相关 最小生成树 有n个城市,m条道路,现在输入n到m的道路的长度,用最少的边将图连接,输出让图连接的最小值 这道题我研究了好长时间才把答案看明白,现在给大家分享一下 具体代码如下 爱被打了一巴掌/ 2022年05月30日 08:56/ 0 赞/ 164 阅读
相关 最小生成树 设G = (V,E)是无向连通带权图,即一个网络。E中的每一条边(v,w)的权为c\[v\]\[w\]。如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成 红太狼/ 2022年05月24日 11:41/ 0 赞/ 252 阅读
相关 最小生成树 问题提出: 要在n个城市间建立通信联络网。顶点:表示城市,权:城市间通信线路的花费代价。希望此通信网花费代价最小。 问题分析: 答案只能从生成树中找,因为要做到任何 雨点打透心脏的1/2处/ 2022年03月18日 12:28/ 0 赞/ 271 阅读
相关 最小生成树 kruskal算法基本思路:先对边按权重从小到大排序,先选取权重最小的一条边,如果该边的两个节点均为不同的分量,则加入到最小生成树,否则计算下一条边,直到遍历完所有的边。 p ゝ一纸荒年。/ 2022年02月25日 14:20/ 0 赞/ 254 阅读
相关 最小生成树 最小生成树 什么是最小生成树 给定一张无向带权图G=(V,E,W),从中挑选出|V|-1条边,使得V中任意两点都有直接或者间接的路径 显然,最后得到的子 浅浅的花香味﹌/ 2021年12月22日 01:57/ 0 赞/ 302 阅读
相关 最小生成树 Jungle Roads [http://poj.org/problem?id=1251][http_poj.org_problem_id_1251] Descrip 逃离我推掉我的手/ 2021年11月29日 22:28/ 0 赞/ 401 阅读
还没有评论,来说两句吧...