最小生成树

  • 子图:从原图中选中一些由节点和边组成的图,称之为原图的子图。
  • 生成子图:选中一些由边和所有节点组成的图,称之为原图的生成子图。
  • 生成树:如果生成的子图恰好是一棵树,则称之为生成树。
  • 最小生成树:权值之和最小的生成树,称之为最小生成树。
  • 求解最小生成树的两种求解算法:Prim算法和Kruskal算法。

Prim算法

算法设计

  1. 初始化。令集合U = {u_0},u_0 \in V,并初始化数组closest[]、lowcost[]和s[]。
  2. 在集合V-U中找lowcost值最小的节点t,即lowcost[t] = min{lowcost[j]|j \in V-U},满足该公式的节点t就是集合V-U中连续集合U的最邻近点。
  3. 将节点t加入集合U中。
  4. 如果集合V-U为空,则算法结束,否则转向步骤5
  5. 对集合V-U中所有节点j都更新其lowcost[]和closest[]。更新if(C[t][j]<lowcost[j]){lowcost[j] = C[t][j];closest[j] = t;},转向步骤2。

算法实现

#include<iostream>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=100;
bool s[N];//如果s[i]=true,说明顶点i已加入U
int c[N][N],closest[N],lowcost[N];
void Prim(int n); //Prim算法构建最小生成树
int main(){int n,m,u,v,w;cin>>n>>m;int sumcost=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)c[i][j]=INF;for(int i=1;i<=m;i++){cin>>u>>v>>w;c[u][v]=c[v][u]=w;}Prim(n);cout<<"数组lowcost:"<<endl;for(int i=1;i<=n;i++)cout<<lowcost[i]<<" ";cout<<endl;for(int i=1;i<=n;i++)sumcost+=lowcost[i];cout<<"最小的花费:"<<sumcost<<endl;return 0;
}
void Prim(int n){s[1]=true; //初始时,集合中U只有一个元素,即顶点1for(int i=1;i<=n;i++){if(i!=1){lowcost[i]=c[1][i];closest[i]=1;s[i]=false;}elselowcost[i]=0;}for(int i=1;i<n;i++){int temp=INF;int t=1;for(int j=1;j<=n;j++){//在集合中V-u中寻找距离集合U最近的顶点tif(!s[j]&&lowcost[j]<temp){t=j;temp=lowcost[j];}}if(t==1)break;//找不到t,跳出循环s[t]=true;//否则,t加入集合Ufor(int j=1;j<=n;j++){ //更新lowcost和closestif(!s[j]&&c[t][j]<lowcost[j]){lowcost[j]=c[t][j];closest[j]=t;}}}
}

输入:

7 12
1 2 23
1 6 28
1 7 36
2 3 20
2 7 1
3 4 15
3 7 4
4 5 3
4 7 9
5 6 17
5 7 16
6 7 25

输出:

数组lowcost:
0 23 4 9 3 17 1
最小的花费:57

Kruskal算法

算法步骤

  1. 初始化。将所有边都按权值从小到大排序,将每个节点的集合号都初始化为自身编号。
  2. 按排序后的顺序选择权值最小的边(u,v)。
  3. 如果节点u和v属于两个不同的连通分支,则将边(u,v)加入边集TE中,并将两个连通分支合并。
  4. 如果选取的边数小于n-1,则转向步骤2,否则算法结束。

算法实现

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100;
int fa[N];
int n,m;
struct Edge{int u,v,w;
}e[N*N];
bool cmp(Edge x, Edge y); //比较函数
void Init(int n); //初始化集合号为自身
int Merge(int a,int b); //合并
int Kruskal(int n); //求最小生成树
int main(){cin>>n>>m;Init(n);for(int i=1;i<=m;i++)cin>>e[i].u>>e[i].v>>e[i].w;cout<<"最小的花费是:"<<Kruskal(n)<<endl;return 0;
}
bool cmp(Edge x, Edge y){return x.w<y.w;
}void Init(int n){//初始化集合号为自身 for(int i=1;i<=n;i++)fa[i]=i;
}int Merge(int a,int b){//合并 int p=fa[a];int q=fa[b];if(p==q) return 0;for(int i=1;i<=n;i++){//检查所有结点,把集合号是q的改为pif(fa[i]==q)fa[i]=p;//a的集合号赋值给b集合号}return 1;
}int Kruskal(int n){//求最小生成树 int ans=0;sort(e,e+m,cmp);for(int i=0;i<m;i++)if(Merge(e[i].u,e[i].v)){ans+=e[i].w;n--;if(n==1)//n-1次合并算法结束 return ans;}return 0;
}

输入:

7 12
1 2 23
1 6 28
1 7 36
2 3 20
2 7 1
3 4 15
3 7 4
4 5 3
4 7 9
5 6 17
5 7 16
6 7 25

输出:

最小的花费是:57

算法优化

  • 如果使用并查集优化合并操作,则每次合并的时间复杂度都为O(logn)。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100;
int fa[N];
int n,m;
struct Edge{int u,v,w;
}e[N*N];
void Init(int n); //初始化集合号为自身
int Find(int x); //找祖宗
bool Merge(int a,int b); //合并
int Kruskal(int n); //最小生成树
int main(){cin>>n>>m;Init(n);for(int i=1;i<=m;i++)cin>>e[i].u>>e[i].v>>e[i].w;cout<<"最小的花费:"<<Kruskal(n)<<endl;return 0;
}bool cmp(Edge x,Edge y){return x.w<y.w;
}void Init(int n){for(int i=1;i<=n;i++)fa[i]=i;
}int Find(int x){if(x!=fa[x])fa[x]=Find(fa[x]);return fa[x];
}bool Merge(int a,int b){int p=Find(a);int q=Find(b);if(p==q) return 0;fa[q]=p;return 1;
}int Kruskal(int n){int ans=0;sort(e,e+m,cmp);for(int i=0;i<m;i++)if(Merge(e[i].u,e[i].v)){ans+=e[i].w;n--;if(n==1)return ans;}return 0;
}

输入:

7 12
1 2 23
1 6 28
1 7 36
2 3 20
2 7 1
3 4 15
3 7 4
4 5 3
4 7 9
5 6 17
5 7 16
6 7 25

输出:

最小的花费是:57

训练1:丛林之路

题目描述

丛林道路网络的维护费用太高,理事会必须选择停止维护一些道路。如下图所示,在地图中,村庄被标记为A~I。左边的地图显示了现在所有道路及每月的维护费用,每月可以用最少的费用维护一些道路,保证所有村庄都是连通的。右边的地图显示了最便宜的道路维护方案,每月的维护总费用为216元。

输入:输入由1~100个数据集组成,最后一行只包含0.每个数据集的第1行都为数字n(1<n<27),表示村庄的数量,对村庄使用字母表的前n个大写字母标记。每个数据集都有n-1行描述,这些行的村庄标签按字母顺序排序。最后一个村庄没有道路。村庄的每条道路都以村庄标签开头,后面跟着一个从这个村庄到后面村庄的道路数k。如果k>0,则改行后面包含k条道路的数据。每条道路的水机都是道路另一端的村庄标签,后面是道路的每月维护成本。维护费用是小于100 的正整数,道路是用户量不会超过75条,每个村庄通往其他村庄的道路都不超过15条

输出:对于每个数据集,都单行输出每月维护连接所有村庄的道路的最低费用。

算法设计

  • 使用Prim或Kruskal算法求最小生成树。需要注意的是,在数据的输入格式方面,A 2 B 12 I 25表示A关联两条边,包括A-B的边(边权为12)及A-I的边(边权为25)。

Prim方法

#include<iostream>
#include<cstring>
using namespace std;
int m[30][30],dis[30];
bool vis[30];
int n;
int prim(int s); //Prim算法求最小生成树
int main(){while(cin>>n&&n){int num,w;char c;memset(m,0x3f,sizeof(m)); //初始化权值数组for(int i=1;i<n;i++){cin>>c>>num;int u=c-'A'; //将村庄标记转换为数值while(num--){cin>>c>>w; //记录村庄子节点int v=c-'A';if(w<m[u][v])m[u][v]=m[v][u]=w;}}cout<<prim(0)<<endl;}return 0;
}
int prim(int s){for(int i=0;i<n;i++)dis[i]=m[s][i];//初始化权值数组memset(vis,false,sizeof(vis));vis[s]=1;//标记已查找int sum=0;int t;for(int i=1;i<n;i++){int min=0x3f3f3f3f;for(int j=0;j<n;j++){//找最小if(!vis[j]&&dis[j]<min){min=dis[j];t=j;}}sum+=min;vis[t]=1;for(int j=0;j<n;j++){//更新if(!vis[j]&&dis[j]>m[t][j])dis[j]=m[t][j];}}return sum;
}

输入:

9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
3
A 2 B 10 C 40
B 1 C 20
0

输出:

216
30

Kruskal方法

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100;
int fa[N];
int n,m = 0;
struct Edge{int u,v,w;
}e[N*N];
void Init(int n); //初始化集合号为自身
int Find(int x); //找祖宗
bool Merge(int a,int b); //合并
int Kruskal(int n); //最小生成树
int main() {while (cin >> n && n) {Init(n);int num, w;char c;for (int i = 1; i < n; i++) {cin >> c >> num;int u = c - 'A'; //将村庄标记转换为数值m = m + num;while (num--) {cin >> c >> w; //记录村庄子节点int v = c - 'A';e[i].u = u;e[i].v = v;e[i].w = w;}}cout << Kruskal(n) << endl;}return 0;
}
bool cmp(Edge x,Edge y){return x.w<y.w;
}void Init(int n){for(int i=1;i<=n;i++)fa[i]=i;
}int Find(int x){if(x!=fa[x])fa[x]=Find(fa[x]);return fa[x];
}bool Merge(int a,int b){int p=Find(a);int q=Find(b);if(p==q) return 0;fa[q]=p;return 1;
}int Kruskal(int n){int ans=0;sort(e,e+m,cmp);for(int i=0;i<m;i++)if(Merge(e[i].u,e[i].v)){ans+=e[i].w;n--;if(n==1)return ans;}return 0;
}

输入:

9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
3
A 2 B 10 C 40
B 1 C 20
0

输出:

216
30

训练2:联网

题目描述

已知该区域中的一组点,以及两点之间每条路线所需的电缆长度。请注意,在两个给定点之间可能存在许多路线。假设给定的可能路线(直接或间接)连接该区域中的每两个点,请设计网络,使每两个点之间都存在连接(直接或间接),并且使用的电缆总长度最小。

输入:输入由多个数据集组成,每个数据集都描述一个网络。数据集的第1行包含两个整数:第1个整数表示点数P(P ≤\leq≤ 50),节点标号为1~P;第2个整数表示点之间的路线数R。以下R行为点之间的路线,每条路线都包括3个整数:前两个正整数为点标号,第3个整数为路线长度L(L ≤\leq≤ 100)。数据集之间以空行分隔,输入仅有一个数字P(P = 0)的数据集,表示输入结束。

输出:对于每个数据集,都单行输出所涉及网络的电缆的最小总长度。

算法实现

#include<iostream>
#include<algorithm>
using namespace std;
int fa[55],n,m,cnt;
struct node{int u,v,cost;
}edge[3000];bool cmp(node x,node y){//定义排序优先级 return x.cost<y.cost;//按权值升序
} void add(int a,int b,int c){edge[cnt].u=a;edge[cnt].v=b;edge[cnt++].cost=c;
}int find(int x){//并查集找祖宗 return fa[x]==x?x:fa[x]=find(fa[x]);
}bool merge(int a,int b){//集合合并int x=find(a);int y=find(b);if(x==y) return 0;fa[y]=x;return 1;
}int kruskal(){int sum=0;sort(edge,edge+m,cmp);for(int i=0;i<m;i++){if(merge(edge[i].u,edge[i].v)){sum+=edge[i].cost;if(--n==1)return sum;}}return 0;
}int main(){int x,y,z;while(cin>>n&&n){   cnt=0;cin>>m;for(int i=1;i<=n;i++)fa[i]=i;for(int i=0;i<m;i++){cin>>x>>y>>z;add(x,y,z);}cout<<kruskal()<<endl;}return 0;
}

输入:

1 02 3
1 2 37
2 1 17
1 2 683 7
1 2 19
2 3 11
3 1 7
1 3 5
2 3 89
3 1 91
1 2 325 7
1 2 5
2 3 7
2 4 8
4 5 11
3 5 10
1 5 6
4 2 120

输出:

0
17
16
26

训练3:空间站

题目描述

空间站由许多单元组成,所有单元都是球形的,在该站成功进入其轨道后不久,每个单元都固定在其预定的位置。两个单元可能彼此接触,甚至重叠。在极端情况下,一单元可能完全包围另一个单元。所有单元都必须连接,因为机组成员应该能够从任何单元走到任何其他单元。如果存在下面三种情况,则可以从单元A走到另一个单元B:

  1. A和B相互接触或重叠;

  2. A和B通过“走廊”连接;

  3. 有一个单元C,从A到C,且从B到C是可能的(传递)。

需要设计一种配置,看看用走廊连接哪些单元可以使整个空间站连通。建造走量的成本与其长度成正比。因此选择走廊总长度最短的计划。

输入:输入由多个数据集组成。每个数据集的第1行都包含一个整数n(0<n≤100)n(0 < n \leq 100)n(0<n≤100),表示单元的数量。以下n行是对单元的描述,其中每一行都包含4个值,表示球体的中心坐标x、y和z,以及球体的半径r,每个值都为小数(小数点后3位)。x、y、z和r均为正数且小于100.0。输入的结尾由包含0的行表示。

输出:对于每个数据集,都单行输出建造走廊的最短总长度(小数点后3位)

注意:如果不需要建造走廊,则走廊的最短总长度为0.000。

算法设计

  • 计算任意两个单元之间的距离,如果两个单元有接触或重叠,则距离为0.000
  • 采用Prim算法求解最小生成树
  • 输出最小生成树的权值之和

算法实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=105;
const double inf=0x3f3f3f3f;//类型double
double m[maxn][maxn],low[maxn];
bool vis[maxn];
int n;
struct cell{double x,y,z,r;//球形单元的圆心,半径
}c[maxn];
double clu(cell c1,cell c2);//计算两个球单元的距离
double prim(int s);//最小生成树
int main(){while(cin>>n&&n){//memset(m,0x3f,sizeof(m));//不可以对浮点数赋值for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(i=j)m[i][j]=0;elsem[i][j]=inf;for(int i=0;i<n;i++)cin>>c[i].x>>c[i].y>>c[i].z>>c[i].r;for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(i!=j)m[i][j]=m[j][i]=clu(c[i],c[j]);printf("%.3lf\n",prim(0));}return 0;
}
double clu(cell c1,cell c2){//计算两个球单元的距离double x=(c1.x-c2.x)*(c1.x-c2.x);double y=(c1.y-c2.y)*(c1.y-c2.y);double z=(c1.z-c2.z)*(c1.z-c2.z);double d=sqrt(x+y+z);if(d-c1.r-c2.r<=0)return 0.000;elsereturn d-c1.r-c2.r;
}double prim(int s){//返回值类型doublefor(int i=0;i<n;i++)low[i]=m[s][i];memset(vis,false,sizeof(vis));vis[s]=1;double sum=0.000;int t;for(int i=1;i<n;i++){//执行n-1次double min=inf;for(int j=0;j<n;j++){//找最小if(!vis[j]&&low[j]<min){min=low[j];t=j;}}sum+=min;vis[t]=1;for(int j=0;j<n;j++){//更新if(!vis[j]&&low[j]>m[t][j])low[j]=m[t][j];}}return sum;
}

输入:

3
10.000 10.000 50.000 10.000
40.000 10.000 50.000 10.000
40.000 40.000 50.000 10.000
2
30.000 30.000 30.000 20.000
40.000 40.000 40.000 20.000
5
5.729 15.143 3.996 25.837
6.013 14.372 4.818 10.671
80.115 63.292 84.477 15.120
64.095 80.924 70.029 14.881
39.472 85.116 71.369 5.553
0

输出:

30.000
0.000
73.834

训练4:道路建设

题目描述

有N个村庄,编号为1~N,需要建造一些道路,使每两个村庄之间都可以相互连接。两个村庄A和B是相连的,当且仅当A和B之间有一条道路,或者存在一个村庄C,A和C相连且C和B相连。已知一些村庄之间已经有一些道路,你的工作是修键一些道路,使所有村庄都连通起来,所有道路的长度之和最小。

输入:第1行是整数N(3≤N≤1003 \leq N \leq 1003≤N≤100),表示村庄的数量;然后是N行,其中第i行包含N个整数,第j个整数表示村庄i和村庄j之间的距离(距离为[1,1000]内的整数);接着是整数Q(0≤Q≤N×(N+1)/2)(0 \leq Q \leq N \times (N+1)/2)(0≤Q≤N×(N+1)/2),表示已建成道路的数量;最后是Q行,每行都包含两个整数a和b(1≤a<b≤N)(1 \leq a < b \leq N)(1≤a<b≤N),表示村庄a和村庄b之间的道路已经建成。

输出:单行输出需要构建的所有道路的最小长度。

算法设计

  • 采用Prim算法求最小生成树。

算法实现

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=105;
const int inf=0x3f3f3f3f;
double m[maxn][maxn],low[maxn];
bool vis[maxn];
int n;
int prim(int s); //prim算法求最小生成树
int main(){int q,a,b;while(cin>>n){for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>m[i][j];cin>>q;while(q--){cin>>a>>b;m[a][b]=m[b][a]=0;}cout<<prim(1)<<endl;}return 0;
}
int prim(int s){memset(vis,false,sizeof(vis));for(int i=1;i<=n;i++)low[i]=m[s][i];vis[s]=1;int sum=0;int t;for(int i=1;i<n;i++){//执行n-1次 int min=inf;for(int j=1;j<=n;j++){//找最小 if(!vis[j]&&low[j]<min){min=low[j];t=j;}}sum+=min;vis[t]=1;for(int j=1;j<=n;j++){//更新if(!vis[j]&&low[j]>m[t][j])low[j]=m[t][j];}}return sum;
}

输出:

3
0 990 692
990 0 179
692 179 0
1
1 2

输入:

179

算法训练营 图的应用(最小生成树)相关推荐

  1. 【算法训练营】 - ⑩ 并查集与图

    [算法训练营] - ⑩ 并查集与图 并查集 并查集特征 并查集的优化 图 图结构的表达 图的面试题如何搞定? 图的数据结构 点 边 图 生成图 图算法 广度优先遍历 深度优先遍历 图的拓扑排序算法 最 ...

  2. 数据结构与算法——31. 图的应用:拓扑排序、强连通分支、最短路径问题、最小生成树

    文章目录 一.拓扑排序(Topological Sort) 实现思路 二.强连通分支 1. 转置的概念 2. 强连通分支算法:Kosaraju算法思路 三.最短路径问题 1. 最短路径问题:Dijks ...

  3. 【数据结构-图】2.多图详解最小生成树(多图详解+实现代码)

    最小生成树: 这个定义有两个约束:最小和树 对于树,从而引出以下三个最小生成树的特点 在图中无环 连接所有图中的点 N个顶点,有N-1条边 最小:指的是生成这棵树的边的权值之和最小 最小生成树的求取有 ...

  4. 覃超-算法训练营 学习方法分享[1] 如何精通一个领域

    转载说明:文章内容来自 极客大学算法训练营. 版权归极客大学.覃超老师以及算法训练营的小伙伴所有.如有涉及侵权,请联系我删除,谢谢. 文章目录 精通一个领域的三步走方式 切碎知识点 1. 切碎知识点 ...

  5. 数据结构与算法之-----图(搜索算法)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己 ...

  6. [经验分享] 覃超算法训练营学习笔记

    本文为覃超算法训练营的课程笔记 推荐学习网站 学习数据结构的动画演示网站 B站 覃超大魔王 Snailclimb/JavaGuide 工欲善其事,必先利其器 simple collaborative ...

  7. 极客时间 算法训练营 毕业总结

    不知不觉8周的算法训练营也接近尾声,这期间训练营对自己的影响有三方面 一方面是收获了刻意练习,终身成长这些可以产生长远影响的思想,这里推荐三本书 卡罗尔·德韦克的<终身成长>.安德斯·艾利 ...

  8. 1道动态规划(搬箱子)、KMP算法、图(Prim算法)、1道哈夫曼树

    1.最长递增子序列 华华要给厂里进一批新箱子共n个(n<=500),编号为1到n,用一个正整数ai(1<=ai<=10000)(1<=i<=n)来表示编号为i的箱子的高度 ...

  9. 数据结构与算法之图的应用

    数据结构与算法之图的应用 图的定义和基本概念 图的实现 数组〈邻接矩阵〉 邻接表 图的应用 最小生成树 Prim(普里姆)算法 Kruskal(克鲁斯卡尔)算法 最短路径 迪克斯特拉算法 拓扑排序 执 ...

最新文章

  1. 牛客练习赛81 E. 小 Q 与函数求和 1( “简单莫比乌斯反演” ,欧拉函数性质)
  2. HTML的标签描述20
  3. 【转】gcc 编译使用动态链接库和静态链接库
  4. 【Android 安全】DEX 加密 ( 多 DEX 加载 | 65535 方法数限制和 MultiDex 配置 | PathClassLoader 类加载源码分析 | DexPathList )
  5. python with contextmanager yield 语法糖
  6. php 查看文件锁定状态_PHP flock 文件锁详细介绍
  7. Webpack 打包太慢?来试试 Bundleless
  8. Angular 根据指定条件动态决定是否显示自定义的popup hover Component
  9. Spring MVC遭遇checkbox的问题解决方式
  10. 【Python】mmSegmentation语义分割框架教程(自定义数据集、训练设定、数据增强)
  11. 假设linux分配给u盘设备名是,嵌入式linux开发基础试卷-应用物理A答案
  12. 【GPS模组】移远EC20 基于Arduino的GPS流速仪
  13. 安卓系统车牌离线识别,优秀的车牌识别算法
  14. 微信铁通服务器地址,铁通dns服务器地址大全
  15. 文件共享服务器(CIFS协议)
  16. 2.OSGI企业应用开发-Eclipse中搭建Felix运行环境
  17. Android 九宫格布局(图片上传、预览)
  18. 适配器模式 : 农村小伙娶乌克兰美女语言不通 翻译软件立功
  19. 服务器信号满格但网速很慢,4G信号满格网速却很慢?一招搞定!
  20. 一幅图对比软件开发框架

热门文章

  1. 多线程和事务之Workaround
  2. Win10 网络显示感叹号或小地球,能联网却提示无法连接到Internet 的解决方法
  3. 学习Pycharm使用方法(一):Pycharm中Make available to all projects的含义是什么
  4. 如何移除unity自带的newtonsoft.json
  5. 信任别人计算机和网络,【媒库文选】人们可能更信任计算机而不是人类
  6. idea中service启动类是灰色,而且启动类经常自动消失解决方法
  7. 阿里云邮免费企业邮箱使用smtp发送邮件失败(SMTP开启、配置问题)
  8. 学习C语言的网站(中文的英文的都有)
  9. C#语言实例源码系列-虚拟键盘
  10. 【Python】Python生成个性二维码