【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解
本节纲要
- 什么是图(network)
- 什么是最小生成树 (minimum spanning tree)
- 最小生成树的算法
什么是图(network)?
这里的图当然不是我们日常说的图片或者地图。通常情况下,我们把图看成是一种由“顶点”和“边”组成的抽象网络。在各个“顶点“间可以由”边“连接起来,使两个顶点间相互关联起来。图的结构可以描述多种复杂的数据对象,应用较为广泛,看下图:
为了更好地说明问题,下面我们看一个比较老套的通信问题:
在各大城市中建设通信网络,如下图所示,每个圆圈代表一座城市,而边上的数字代表了建立通信连接的价格。那么,请问怎样才能以最小的价格使各大城市能直接或者间接地连接起来呢?
我们需要注意两点:
- 最小的价格
- 各大城市可以是直接或者间接相连的
稍稍留心可以发现,题目的要求是,城市只需要直接或者间接相连,因此,为了节省成本,我们稍稍优化一下上述方案如下:
可以看到,我们砍掉了原先在AD,BE之间的两条道路,建设价格自然就降下来了。当然这个方案也是符合我们题目的要求的。按照国际惯例,这里要说蛋是了。上面的实例由于数据很简单,优化的方案很easy就看出来了。但在实际中,数据量往往是非常庞大的。所以,我们更倾向于设计一种方法,然后利用计算机强大的运算能力帮我们处理这些数据得出最优的方案。
那么,针对上述问题,我们一起来看看如何应用图的相关知识来实现吧。
什么是最小生成树(minimum spanning tree)
为了直观,还是用图片给大家解释一下:
对于一个图而言,它可以生成很多树,如右侧图2,图3就是由图1生成的。
从上面可以看出生成树是将原图的全部顶点以最少的边连通的子图,对于有n个顶点的连通图,生成树有n-1条边,若边数小于此数就不可能将各顶点连通,如果边的数量多于n-1条边,必定会产生回路。
对于一个带权连通图,生成树不同,树中各边上权值总和也不同,权值总和最小的生成树则称为图的最小生成树。
关于最小生成树的算法(Prim算法和Kruskal算法)
Prim算法
基本思想:
假设有一个无向带权图G=(V,E),它的最小生成树为MinTree=(V,T),其中V为顶点集合,T为边的集合。求边的集合T的步骤如下:
①令 U={u0},T={}。其中U为最小生成树的顶点集合,开始时U中只含有顶点u0(u0可以为集合V中任意一项),在开始构造最小生成树时我们从u0出发。
②对所有u∈U,v∈(V – U)(其中u,v表示顶点)的边(u,v)中,找一条权值最小的边(u’,v’),将这条边加入到集合T中,将顶点v’加入集合U中。
③直到将V中所有顶点加入U中,则算法结束,否则一直重复以上两步。
④符号说明:我们用大写字母表示集合,用小写字母表示顶点元素,用<>表示两点之间的边。
为了更好的说明问题,我们下面一步一步来为大家展示这个过程。
- 初始状态:U={a} V={b,c,d,e } T={}
- 集合U和V相关联的权值最小的边是<a,b style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,于是我们将b加入U。U={a,b},V={d,c,e },T={<a,b style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">}
- 此时集合U和V相关联的权值最小的边是<b,c style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,于是我们将c加入U。U={a,b,c} ,V={d,e },T={<a,b style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">, <b,c style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">}
- 显然此时集合U和V中相关联的权值最小的边是<c,d style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,于是我们将d加入U。U={a,b,c,d} ,V={e },T={<a,b style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">, <b,c style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,<c,d style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">}
- 最后集合U和V中相关联的权值最小的边是<d,e style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,于是将e加入U。U={a,b,c,d,e} ,V={},T={<a,b style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">, <b,c style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,<c,d style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">,<d,e style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">}。
到此所有点访问完毕。
代码实现
1//prime算法
2//将城市X标记为visit=true时,就表示该城市加入到集合U,用sum累加记录边的总费用
3
4#include<iostream>
5#define NO 99999999 //99999999代表两点之间不可达
6#define N 5
7using namespace std;
8
9bool visit[N];
10long long money[N] = { 0 };
11long long graph[N][N] = {0};
12
13void initgraph()
14{
15 for (int i = 0; i < N; i++)
16 {
17 for (int j = 0; j < N; j++)
18 {
19 scanf(" %lld", &graph[i][j]);
20 }
21 }
22
23}
24
25void printgraph()
26{
27 for (int i = 0; i < N; i++)
28 {
29 for (int j = 0; j < N; j++)
30 {
31 printf(" %lld", graph[i][j]);
32 }
33 }
34
35}
36
37int prim(int city)
38{
39 initgraph();
40 printgraph();
41 int index = city;
42 int sum = 0;
43 int i = 0;
44 int j = 0;
45 cout <<"访问节点:" <<index << "\n";
46 memset(visit, false, sizeof(visit));
47 visit[city] = true;
48 for (i = 0; i < N; i++)
49 {
50 money[i] = graph[city][i];//初始化,每个与城市city间相连的费用存入money,以便后续比较
51 }
52
53 for (i = 1; i < N; i++)
54 {
55 int minor = NO;
56 for (j = 0; j < N; j++)
57 {
58 if ((visit[j] == false) && money[j] < minor) //找到未访问的城市中,与当前最小生成树中的城市间费用最小的城市
59 {
60 minor = money[j];
61 index = j;
62 }
63 }
64 visit[index] = true;
65 cout << "访问节点:" << index << "\n";
66 sum += minor; //求总的最低费用
67 /*这里是一个更新,如果未访问城市与当前城市间的费用更低,就更新money,保存更低的费用*/
68 for (j = 0; j < N; j++)
69 {
70 if ((visit[j] == false) && money[j]>graph[index][j])
71 {
72 money[j] = graph[index][j];
73 }
74 }
75 }
76 cout << endl;
77 return sum; //返回总费用最小值
78}
79int main()
80{
81 cout << "修路最低总费用为:"<< prim(0) << endl;//从城市0开始
82 return 0;
83}
Kruskal算法
解最小生成树的另一种常见的算法是Kruskal算法,它比Prim算法更直观。
Kruskal算法的做法是:每次都从剩余边中选取权值最小的,当然,这条边不能使已有的边产生回路。手动求解会发现Kruskal算法异常简单,下面是一个例子
先对边的权值排个序:
1(V0,V4)、2(V2,V6)、4(V1,V3)、6(V1,V2)、8(V3,V6)、10(V5,V6)、12(V3,V5)、15(V4,V5)、20(V0,V1)
首选边1(V0,V4)、2(V2,V6)、4(V1,V3)、6(V1,V2),此时的图是这样
显然,若选取边8(V3,V6)则会出现环,则必须抛弃8(V3,V6),选择下一条10(V5,V6)没有问题,此时图变成这样
显然,12(V3,V5)同样不可取,选取15(V4,V5),边数已达到要求,算法结束。最终的图是这样的
算法逻辑很容易理解,但用代码判断当前边是否会引起环的出现则很棘手。这里简单提一提连通分量
- 在无向图中,如果从顶点vi到顶点vj有路径,则称vi和vj连通。如果图中任意两个顶点之间都连通,则称该图为连通图,否则,将其中较大的连通子图称为连通分量。
- 在有向图中,如果对于每一对顶点vi和vj,从vi到vj和从vj到vi都有路径,则称该图为强连通图;否则,将其中的极大连通子图称为强连通分量。
算法说明
为了判断环的出现,我们换个角度来理解Kruskal算法的做法:初始时,把图中的n个顶点看成是独立的n个连通分量,从树的角度看,也是n个根节点。我们选边的标准是这样的:若边上的两个顶点从属于两个不同的连通分量,则此边可取,否则考察下一条权值最小的边。
于是问题又来了,如何判断两个顶点是否属于同一个连通分量呢?这个可以参照并查集的做法解决。它的思路是:如果两个顶点的根节点是一样的,则显然是属于同一个连通分量。这也同样暗示着:在加入新边时,要更新父节点。
1//kruskal算法
2
3#include<cstdio>
4#include<iostream>
5#include<cstring>
6#include<cstdlib>
7#include<algorithm>
8#include<cmath>
9#include<map>
10#include<set>
11#include<list>
12#include<vector>
13using namespace std;
14#define N 10005
15#define M 50005
16#define qm 100005
17#define INF 2147483647
18struct arr{
19 int ff, tt, ww;
20}c[M << 1];// 存储边的集合,ff,tt,ww为一条从ff连接到tt的权值为ww的边
21int tot = 0;//边的总数
22int ans = 0;//最小生成树的权值和
23int f[N];//并查集
24bool comp(const arr & a, const arr & b){
25 return a.ww < b.ww;
26}
27int m, n;//边数量,点数量
28int getfa(int x){
29 return f[x] == x ? x : f[x] = getfa(f[x]);
30}//并查集,带路径压缩
31
32inline void add(int x, int y, int z){
33 c[++tot].ff = x;
34 c[tot].tt = y;
35 c[tot].ww = z;
36 return;
37}//新增一条边
38
39void kruscal(){
40 for (int i = 1; i <= n; i ++) f[i] = i;
41 for (int i = 1; i <= m; i ++){
42 int fx = getfa(c[i].ff);//寻找祖先
43 int fy = getfa(c[i].tt);
44 if (fx != fy){//不在一个集合,合并加入一条边
45 f[fx] = fy;
46 ans += c[i].ww;
47 }
48 }
49
50 return;
51}
52int main(){
53 freopen("input10.txt", "r", stdin);
54 freopen("output10.txt", "w", stdout);
55 scanf("%d%d",&n, &m);
56 int x, y, z;
57 for (int i = 1; i <= m; i ++){
58 scanf("%d %d %d", &x, &y, &z);
59 add(x, y, z);
60 }
61 sort(c + 1, c + 1 + m, comp);//快速排序
62 kruscal();
63 printf("%d\n", ans);
64 return 0;
65}
【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解的更多相关文章
- 最小生成树(Minimum Spanning Tree)——Prim算法与Kruskal算法+并查集
最小生成树——Minimum Spanning Tree,是图论中比较重要的模型,通常用于解决实际生活中的路径代价最小一类的问题.我们首先用通俗的语言解释它的定义: 对于有n个节点的有权无向连通图,寻 ...
- Prim算法和Kruskal算法(图论中的最小生成树算法)
最小生成树在一个图中可以有多个,但是如果一个图中边的权值互不相同的话,那么最小生成树只可能存在一个,用反证法很容易就证明出来了. 当然最小生成树也是一个图中包含所有节点的权值和最低的子图. 在一个图中 ...
- 最小生成树 (Minimum Spanning Tree,MST) --- Prim算法
本文链接:http://www.cnblogs.com/Ash-ly/p/5409904.html 普瑞姆(Prim)算法: 假设N = (V, {E})是连通网,TE是N上最小生成树边的集合,U是是 ...
- 最小生成树 (Minimum Spanning Tree,MST) --- Kruskal算法
本文链接:http://www.cnblogs.com/Ash-ly/p/5409265.html 引导问题: 假设要在N个城市之间建立通信联络网,则连通N个城市只需要N - 1条线路.这时,自然会考 ...
- Prim算法、Kruskal算法和最小生成树 | Minimum Spanning Tree
graph to tree非常有趣! 距离的度量会极大地影响后续的分析,欧式距离会放大差异,相关性会缩小差异,导致某些细胞群分不开. 先直观看一下,第一个是Prim,第二个是Kruskal.但是肯定都 ...
- 算法练习:最小生成树 (Minimum Spanning Tree)
(注:此贴是为了回答同事提出的一个问题而匆匆写就,算法代码只求得出答案为目的,效率方面还有很大的改进空间) 最小生成树是指对于给定的带权无向图,需要生成一个总权重最小的连通图.其问题描述及算法可以详见 ...
- 数据结构与算法分析–Minimum Spanning Tree(最小生成树)
给定一个无向图,如果他的某个子图中,任意两个顶点都能互相连通并且是一棵树,那么这棵树就叫做生成树(spanning tree). 如果边上有权值,那么使得边权和最小的生成树叫做最小生成树(MST,Mi ...
- HDU 4408 Minimum Spanning Tree 最小生成树计数
Minimum Spanning Tree Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Ot ...
- 说说最小生成树(Minimum Spanning Tree)
minimum spanning tree(MST) 最小生成树是连通无向带权图的一个子图,要求 能够连接图中的所有顶点.无环.路径的权重和为所有路径中最小的. graph-cut 对图的一个切割或者 ...
随机推荐
- JavaScript之BON
1.windows对象 全局作用域: 2.窗口关系及框架 如果页面包含框架,则每个框架都有自己的window对象,并且保存在iframes集合中,在iframe集合中,可以通过数值索引(从0开始,从左 ...
- Python基础学习三 字符串
字符串方法 slit = ['a', 'b', 'c', 'd', 'f', 'g'] s2='hhhhhhhhhh' tu = (1,2,3,4,5) d={'name':'nnn','age':1 ...
- Unable to find required classes (javax.activation.DataHandler and javax.mail.internet.MimeMultipart)
在接触WebService时值得收藏的一篇文章: 在调试Axis1.4访问WebService服务时,出现以下错误: Unable to find required classes (javax.ac ...
- js中 visibility和display的区别 js中 visibility和display的区别
大多数人很容易将CSS属性display和visibility混淆,它们看似没有什么不同,其实它们的差别却是很大的. visibility属性用来确定元素是显示还是隐藏,这用visibility=&q ...
- codeforce469DIV2——D. A Leapfrog in the Array
题意: 给出1<=n<=10^18和1<=q<=200000,有一个长度为2*n-1的数组,初始时单数位置存(i+1)/2,双数位置是空的.每次找出最右边的一个数将它跳到离它最 ...
- 无限极分类的JS实现
纯JS实现无限极分类 <!DOCTYPE html> <html> <head> <title></title>//引入Jquery < ...
- java面试题 级hr解答 非技术问题 !=!=未看
Java基础 ● 集合类以及集合框架:HashMap与HashTable实现原理,线程安全性,hash冲突及处理算法:ConcurrentHashMap: ● 进程和线程的区别: ● Java的并发. ...
- Mock Server实践
转载自 https://tech.meituan.com/mock-server-in-action.html 背景 在美团服务端测试中,被测服务通常依赖于一系列的外部模块,被测服务与外部模块间通过R ...
- .net对Cookie的简单操作
1 声明:HttpCookie MyCookie= new HttpCookie("test"); 2增加:MyCookie.Values.Add("key1" ...
- 安全、结构良好的jQuery结构模板
安全.结构良好的jQuery结构模板 ;(function($,window,document,undefined){ //我们的代码- })(jQuery,window,document); 参 ...