写在前面:我是一只蒟蒻。
好的,今天我们来讲一讲“最小生成树”。
首先,我们来看看什么是最小生成树,
定义:
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。(摘自百度百科)
定理:任意一棵最小生成树一定包含无向图中权值最小的边。
证明:
反证法。假设无向图G=(V,E)存在一棵最小生成树不包含权值最小的边。设边e(x,y,z)是无向图中权值最小的边。把e加入到树中,e会和树上的x,y构成一个环,且e比环上任意一条边要小,所以用e来替换环上的任意一条边,就会得到比原树更小的生成树,与结论矛盾,所以此结论成立。
对于这样的一个问题,我们有什么算法来解决呢?
下面,我就给大家来介绍一下解决这类问题的两种算法:
kruskal算法
首先,我们从先从定义来看,要找最小生成树,就要先找边权最短,所以我们就可以先排序(以边权值从小到大),然后再寻找两边能否使图连通,如果可以就加入这棵树,这时我们就需要使用并查集来维护。
下面是伪代码流程:
1、首先我们先用并查集将每个点的祖先初始化
2、排序
3、扫描每条边,判断是否是联通两点的最短边
下面是图形的演示
代码:
1 #include<bits/stdc++.h> 2 #define N 200007 3 #define maxn 5007 4 using namespace std; 5 struct edge{ 6 int x,w,y; 7 }e[N*2]; 8 int head[maxn],tot,num,ans,fa[maxn],n,m; 9 bool cmp(edge a,edge b){ 10 return a.w<b.w; 11 } 12 int find(int x){ 13 if(x==fa[x])return x; 14 fa[x]=find(fa[x]); 15 return fa[x]; 16 } 17 void kruskal(){ 18 for(int i=1;i<=n;i++)fa[i]=i; 19 for(int i=1;i<=m;i++){ 20 int x=e[i].x,y=e[i].y; 21 if(find(x)==find(y))continue; 22 fa[find(x)]=find(y); 23 ans+=e[i].w; 24 num++; 25 if(num==n-1)break; 26 } 27 } 28 29 int main(){ 30 scanf("%d%d",&n,&m); 31 for(int i=1;i<=m;i++){ 32 scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w); 33 } 34 sort(e+1,e+1+m,cmp); 35 kruskal(); 36 printf("%d ",ans); 37 return 0; 38 }
看完模板代码,我们来看一道例板子题:洛谷P1546
题目:
题目背景
农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。
题目描述
约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。
你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000
输入输出格式
输入格式:
第一行: 农场的个数,N(3<=N<=100)。
第二行..结尾: 后来的行包含了一个N*N的矩阵,表示每个农场之间的距离。理论上,他们是N行,每行由N个用空格分隔的数组成,实际上,他们限制在80个字符,因此,某些行会紧接着另一些行。当然,对角线将会是0,因为不会有线路从第i个农场到它本身。
输出格式:
只有一个输出,其中包含连接到每个农场的光纤的最小长度。
输入输出样例
4 0 4 9 21 4 0 8 17 9 8 0 16 21 17 16 0
28
这道题,我们读完题就可以十分十分简单的看出这道题是道图论(废话,不说我也能看出来 )。。。。。。
再一看,我们又看到我们就要建树,为了满足题意的条件,我们就要找最小生成树。
nice!下一道。(本题AC代码放置博客最后)
题目:
题目描述
城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:
1.改造的那些道路能够把所有的交叉路口直接或间接的连通起来。 2.在满足要求1的情况下,改造的道路尽量少。 3.在满足要求1、2的情况下,改造的那些道路中分值最大的道路分值尽量小。
任务:作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。
输入输出格式
输入格式:第一行有两个整数n,m表示城市有n个交叉路口,m条道路。
接下来m行是对每条道路的描述,u, v, c表示交叉路口u和v之间有道路相连,分值为c。(1≤n≤300,1≤c≤10000,1≤m≤100000)
输出格式:两个整数s, max,表示你选出了几条道路,分值最大的那条道路的分值是多少。
输入输出样例
4 5 1 2 3 1 4 5 2 4 7 2 3 6 3 4 8
3 6
这道题在上一道题上又多了一个新的概念“瓶颈生成树”,什么是瓶颈生成树,很简单,我们来看一下它的定义:
无向图G的一颗瓶颈生成树是这样的一颗生成树T,它最大的边权值在G的所有生成树中是最小的。瓶颈生成树的值为T中最大权值边的。
一个结论:无向图的最小生成树一定是瓶颈生成树,但瓶颈生成树不一定是最小生成树。
其正确性就不在此证明了,这道题就是一颗瓶颈生成树,我们只需要在找最小生成树时统计一下最小边加的边数,最后输出就可以了。
6月26日更新
非常抱歉,当时在写完这部分之后就忘了还缺内容,今天在做到最小生成树的题的时候,才想起来。
补得代码:

1 #include<bits/stdc++.h> 2 using namespace std; 3 #define maxn 200002 4 struct edge{ 5 int x,y,w; 6 }e[maxn*2]; 7 int head[maxn],tot,n,ans,p=1,fa[maxn]; 8 void add(int a,int b,int c){ 9 e[++tot].w=c; 10 e[tot].x=a; 11 e[tot].y=b; 12 } 13 int cmp(edge a,edge b){ 14 return a.w<b.w; 15 } 16 int find(int x){ 17 if(fa[x]==x)return x; 18 fa[x]=find(fa[x]); 19 return fa[x]; 20 } 21 void kruskal(){ 22 for(int i=1;i<=n;i++)fa[i]=i; 23 for(int i=1;i<=tot;i++){ 24 int x=e[i].x,y=e[i].y; 25 if(find(x)==find(y))continue; 26 fa[find(x)]=find(y); 27 ans+=e[i].w; 28 p++; 29 if(p==n)break; 30 } 31 } 32 33 34 int main(){ 35 scanf("%d",&n); 36 for(int i=1;i<=n;i++){ 37 for(int j=1;j<=n;j++){ 38 int a; 39 scanf("%d",&a); 40 if(i<j){ 41 add(i,j,a); 42 add(j,i,a); 43 } 44 } 45 } 46 sort(e+1,e+1+tot,cmp); 47 kruskal(); 48 printf("%d ",ans); 49 50 51 return 0; 52 }

1 #include<bits/stdc++.h> 2 using namespace std; 3 #define maxn 100007 4 #define N 308 5 struct edge{ 6 int x,y,w; 7 }e[maxn*2]; 8 int head[N],tot,ans,maxx=0,fa[N],n,m; 9 void add(int a,int b,int c){ 10 e[++tot].x=a; 11 e[tot].w=c; 12 e[tot].y=b; 13 } 14 int cmp(edge a,edge b){ 15 return a.w<b.w; 16 } 17 int find(int x){ 18 if(fa[x]==x)return fa[x]; 19 fa[x]=find(fa[x]); 20 return fa[x]; 21 } 22 void kruskal(){ 23 for(int i=1;i<=n;i++)fa[i]=i; 24 for(int i=1;i<=tot;i++){ 25 int x=find(e[i].x); 26 int y=find(e[i].y); 27 if(x==y)continue; 28 fa[x]=y; 29 ans++; 30 maxx=max(e[i].w,maxx); 31 } 32 } 33 int main(){ 34 scanf("%d%d",&n,&m); 35 for(int i=1;i<=m;i++){ 36 int a,b,c; 37 scanf("%d%d%d",&a,&b,&c); 38 add(a,b,c); 39 add(b,a,c); 40 } 41 sort(e+1,e+1+tot,cmp); 42 kruskal(); 43 printf("%d %d ",ans,maxx); 44 return 0; 45 }
下面进入主题:
好久之前我们介绍了kruskal算法求最小生成树
今天,我们来看一看
Prim算法
Prim算法与kruskal算法的核心思想是一样的,但是思路和操作的方式有些不同,Prim总是维护最小生成树的一部分。在最开始,其只是确定最小生成树的一个点。
操作流程:
① 先建立一个只有一个结点的树,这个结点可以是原图中任
意的一个结点。
② 使用一条边扩展这个树,要求这条边一个顶点在树中另一
个顶点不在树中,并且这条边的权值要求最小。
③ 重复步骤②直到所有顶点都在树中。
模板:
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 5010 4 int a[N][N],d[N],n,m,ans; 5 bool vis[N];//指示该点是否在最小生成树中 6 void prim(){ 7 memset(d,0x3f3f3f3f,sizeof(d)); 8 memset(vis,0,sizeof(vis)); 9 d[1]=0;//初始点 10 for(int i=1;i<n;i++){ 11 int x=0; 12 for(int j=1;j<=n;j++){ 13 if(!vis[j]&&d[j]<d[x])x=j; 14 } 15 vis[x]=1;//加入树中 16 for(int j=1;j<=n;j++){ 17 if(!vis[j])d[j]=min(d[j],a[x][j]); 18 } 19 } 20 } 21 22 23 int main(){ 24 scanf("%d%d",&n,&m); 25 memset(a,0x3f3f3f3f,sizeof(a)); 26 for(int i=1;i<=n;i++){ 27 a[i][i]=0; 28 } 29 for(int i=1;i<=m;i++){ 30 int x,y,z; 31 scanf("%d%d%d",&x,&y,&z); 32 a[y][x]=a[x][y]=min(a[x][y],z); 33 } 34 prim(); 35 for(int i=2;i<=n;i++)ans+=d[i]; 36 printf("%d",ans); 37 38 39 40 return 0; 41 }
Prim 主要用于稠密图,完全图。
下面给大家推荐一道题
代码(前面的kruskal。。。是错的):

1 /*#include<bits/stdc++.h> 2 using namespace std; 3 struct node{ 4 int x,y; 5 double w; 6 }e[10007]; 7 double calc(int x,int y,int xx,int yy){ 8 return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));//求两点距离 9 } 10 int head[5007],fa[5007],n,cnt,dis[5007][5007]; 11 struct NODE{ 12 int x,y; 13 }a[5007]; 14 int find(int x){ 15 if(fa[x]==x)return fa[x]; 16 fa[x]=find(fa[x]); 17 return fa[x]; 18 } 19 bool cmp(node a,node b){ 20 return a.w<b.w; 21 } 22 23 void add(int a,int b,double c){ 24 e[++cnt].x=a; 25 e[cnt].y=b; 26 e[cnt].w=c; 27 } 28 29 // double 30 31 32 int main(){ 33 scanf("%d",&n); 34 for(int i=1;i<=n;i++){ 35 // int a,b; 36 scanf("%d%d",&a[i].x,&a[i].y); 37 } 38 for(int i=1;i<=n;i++){ 39 for(int j=1;j<=n;j++){ 40 if(i==j)continue; 41 add(i,j,calc(a[i].x,a[i].y,a[j].x,a[j].y)); 42 fa[i]=i; 43 } 44 } 45 double ans=0; 46 sort(e+1,e+1+cnt,cmp); 47 for(int i=1;i<=cnt;i++){ 48 int x=e[i].x; 49 int y=e[i].y; 50 int f1=find(x); 51 int f2=find(y); 52 if(f1==f2){ 53 continue; 54 } 55 fa[f1]=f2; 56 ans+=e[i].w; 57 } 58 printf("%0.2f",ans); 59 return 0; 60 }*///kruskal 61 //prim算法 62 #include<bits/stdc++.h> 63 using namespace std; 64 #define N 5007 65 #define INF 100000000 66 template <typename type> 67 void scan(type &x){ 68 type f=1;x=0;char s=getchar(); 69 while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} 70 while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} 71 x*=f; 72 } 73 struct node{ 74 int x,y; 75 }a[N]; 76 double dis[N],ans; 77 bool vis[N];//记录数组 78 int n; 79 double calc(int x,int y,int xx,int yy){ 80 return sqrt((double)(x-xx)*(x-xx)+(double)(y-yy)*(y-yy));//求两点距离 81 } 82 int main(){ 83 scan(n); 84 for(int i=1;i<=n;i++){ 85 scan(a[i].x); 86 scan(a[i].y); 87 dis[i]=INF; 88 } 89 dis[1]=0; 90 int k; 91 for(int i=1;i<=n;i++){ 92 double minn=INF; 93 for(int j=1;j<=n;j++){ 94 if(!vis[j]&&dis[j]<minn){ 95 minn=dis[j]; 96 k=j;//取最优点 97 } 98 } 99 ans+=minn;vis[k]=1;//最优点加入树中 100 for(int j=1;j<=n;j++){ 101 double d=calc(a[k].x,a[k].y,a[j].x,a[j].y); 102 if(dis[j]>d) dis[j]=d; 103 } 104 } 105 printf("%.2lf",ans); 106 return 0; 107 }