最小生成树
简介:在无向图中,连通且不含圈的图称为树;
给定无向图G=(V,E),连接G中的所有点,且边集是E的子集的树称为G的生成树;
而权值最小的生成树称为最小生成树(MST)
- 两种常见的方法:
1.Prim算法
图例 | 说明 | 不可选 | 可选 | 已选(Vnew) |
---|---|---|---|---|
|
此为原始的加权连通图。每条边一侧的数字代表其权值。 | - | - | - |
|
顶点D被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 | C, G | A, B, E, F | D |
|
下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。 | C, G | B, E, F | A, D |
![]() |
算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 | C | B, E, G | A, D, F |
|
在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。E最近,因此将顶点E与相应边BE高亮表示。 | 无 | C, E, G | A, D, F, B |
|
这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。 | 无 | C, G | A, D, F, B, E |
|
顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。 | 无 | G | A, D, F, B, E, C |
|
现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 | 无 | 无 | A, D, F, B, E, C, G |
- 思路简述:首先,定义顶点集合V和边集合E(初始化都是空集)然后找到一个起点,再在集合中找到所有与该点直接相连的点,找到边权最小的,收入记录最小生成树的集合。以此类推,直到所有定点都被记录在最小生成树中。
- 例题:
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 58599 | Accepted: 24300 |
Description
Input
Output
Sample Input
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
Sample Output
28
Source
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cmath> 3 #include <cstring> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <algorithm> 7 #define MAXN 1010101 8 using namespace std; 9 int map[501][501]; //map是边集 10 int low[10101]; //每个点到当前pos的最短边 11 bool vis[10101]; //记录当前位置是否被访问过 12 int n; 13 int ans; 14 void prim(int pos) 15 { 16 ans=0; // 17 memset(vis,0,sizeof(vis)); //这两行的初始化很重要 18 vis[pos]=1; 19 for(int i=1;i<=n;i++) 20 if(i!=pos) low[i]=map[pos][i]; //在每组数据的最开始初始化所有点到pos=1的距离 21 for(int i=1;i<n;i++) //把未纳入最小生成树的点都遍历一遍 22 { 23 int minn=MAXN; 24 for(int j=1;j<=n;j++) 25 if(vis[j]==0 && low[j]<minn) //找最小距离 26 { 27 minn=low[j]; 28 pos=j; 29 } 30 vis[pos]=1; 31 ans+=minn; //更新结果 32 for(int j=1;j<=n;j++) //更新权值 33 if(vis[j]==0 && low[j]>map[pos][j]) 34 low[j]=map[pos][j]; 35 } 36 } 37 38 int main() 39 { 40 while(cin>>n) 41 { 42 memset(map,MAXN,sizeof(map)); 43 for(int i=1;i<=n;i++) 44 for(int j=1;j<=n;j++) 45 { 46 int x; 47 scanf("%d",&x); 48 map[i][j]=map[j][i]=x; 49 } 50 prim(1); 51 printf("%d\n",ans); 52 } 53 //system("pause"); 54 return 0; 55 }
2.Kruskal 算法
首先第一步,我们有一张图Graph,有若干点和边
将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资 源进行选择,排序完成后,我们率先选择了边AD。这样我们的图就变成了左图
在剩下的边中寻找。我们找到了CE。这里边的权重也是5
依次类推我们找到了6,7,7,即DF,AB,BE。(在找到AB和BE时,发现他们两端的顶点不属于同一个连通分量,因此加上了这 两条边)
下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连 接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示 了)。
- 思路简述:先把边按权排序,之后依次判断每条边的两个顶点是否属于同一个联通分量(即一个连通子图)。若属于,这条边就不应加上,因为会形成一个环,说明这条边是多余的;若不属于,就应加上。实现的重点是判断,因此要用到并查集。实现起来很便捷,且数量级近似于常数级。
- 例题:
Bad Cowtractors
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 15278 | Accepted: 6276 |
Description
Realizing Farmer John will not pay her, Bessie decides to do the worst job possible. She must decide on a set of connections to install so that (i) the total cost of these connections is as large as possible, (ii) all the barns are connected together (so that it is possible to reach any barn from any other barn via a path of installed connections), and (iii) so that there are no cycles among the connections (which Farmer John would easily be able to detect). Conditions (ii) and (iii) ensure that the final set of connections will look like a "tree".
Input
* Lines 2..M+1: Each line contains three space-separated integers A, B, and C that describe a connection route between barns A and B of cost C.
Output
Sample Input
5 8
1 2 3
1 3 7
2 3 10
2 4 4
2 5 8
3 4 6
3 5 2
4 5 17
Sample Output
42
Hint
The most expensive tree has cost 17 + 8 + 10 + 7 = 42. It uses the following connections: 4 to 5, 2 to 5, 2 to 3, and 1 to 3.
Source
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cmath> 3 #include <cstring> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <algorithm> 7 #define MAXN 20101 8 using namespace std; 9 int n,m; 10 int u[MAXN],v[MAXN],c[101010]; 11 int p[1010],r[MAXN]; 12 int num=1; //记录能否包含所有点,初始化即为一个点 13 int cmp(const int i,const int j) {return c[i]>c[j];} //从大到小排序 14 int find(int x) {return p[x] == x ? x : p[x]=find(p[x]);} //找祖先 15 int kruskal() 16 { 17 int sum=0; 18 for(int i=1;i<=m;i++) 19 { 20 int e=r[i]; 21 int x=find(u[e]),y=find(v[e]); //判断他两端的顶点是否属于一个连通分量 22 if(x!=y) 23 { 24 sum+=c[e]; 25 p[x]=y; 26 num++; 27 } 28 } 29 return sum; 30 } 31 32 int main() 33 { 34 scanf("%d%d",&n,&m); 35 for(int i=1;i<=m;i++) 36 scanf("%d%d%d",&u[i],&v[i],&c[i]); 37 for(int i=1;i<=n;i++) p[i]=i; //初始化 38 for(int i=1;i<=m;i++) r[i]=i; 39 sort(r+1,r+m+1,cmp); 40 int ans=kruskal(); 41 if(num<n) printf("-1"); 42 else printf("%d",ans); 43 //system("pause"); 44 return 0; 45 }
特别注意:代码里的u,v数组记录的是每条边两端的点,而不是点的个数!!!所以这两个数组的范围应该是边m的范围。