最小生成树

写在前面:我是一只蒟蒻。

好的,今天我们来讲一讲“最小生成树”。

首先,我们来看看什么是最小生成树,

定义:

一个有 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个农场到它本身。

输出格式:

只有一个输出,其中包含连接到每个农场的光纤的最小长度。

输入输出样例

输入样例#1:
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
输出样例#1:
28

这道题,我们读完题就可以十分十分简单的看出这道题是道图论(废话,不说我也能看出来 )。。。。。。

再一看,我们又看到我们就要建树,为了满足题意的条件,我们就要找最小生成树。

nice!下一道。(本题AC代码放置博客最后)

 洛谷P2330

题目:

题目描述

城市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,表示你选出了几条道路,分值最大的那条道路的分值是多少。

输入输出样例

输入样例#1:
4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8
输出样例#1:
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 }
P1546
 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 }
P2330

下面进入主题:

好久之前我们介绍了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 主要用于稠密图,完全图。

下面给大家推荐一道题

洛谷P1265

代码(前面的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 }
P1265
原文地址:https://www.cnblogs.com/xishirujin/p/10432862.html