最小生成树——Minimum Spanning Tree,是图论中比较重要的模型,通常用于解决实际生活中的路径代价最小一类的问题。我们首先用通俗的语言解释它的定义:

对于有n个节点的有权无向连通图,寻找n-1条边,恰好将这n个节点相连,并且这n-1条边的权值之和最小。

对于MST问题,通常常见的解法有两种:Prim算法   或者  Kruskal算法+并查集

对于最小生成树,一定要注意其定义是在无向连通图的基础上,如果在有向图中,那么就需要另外的分析,单纯用无向图中的方法是不能得出正确解的,这一点我在比赛中确实吃过亏

好了,进入正题:

Prim算法:(基于点的贪心思路)由于是基于点的算法,因此适合于稠密图,一下给出代码没有经过堆优化,时间复杂度为O(N^2)

  记原图为G,生成树图为MST,其中G的节点个数为n个

  算法描述如下:

  1. 任取G中的一点,加入MST中——这一步的作用是选择一个节点作为整个算法的起点
  2. 采用贪心策略,将刚刚加入的节点记为u,以u为中心,检查与u相连且没有加入MST的节点(未访问过的节点),选择权值最小的边,如果有多条边的权值均最小,则任取一条边。——贪心策略,选择局部最优
  3. 将所选择的边中,不在MST中的那个节点,加入MST——举例来说,比如(u,v)是当前与u相连,v不再MST中,且权值最小的边,则边(u,v)被选中,并将v加入MST。
  4. 如果步骤2-3被执行了n-1次,则退出,反之则返回到步骤2。——由于Prim算法初始化时加入了起点,而步骤2-3每执行一次都会加入一个新的节点,所以只需判断执行次数。

关于算法的正确性证明网上都有证明,这里就不再赘述。

  1. //inf为路径权上界,maxn为图的临接矩阵的点数
  2. //vis是记录是否访问过,cost[i]记录到达第i个节点的最小代价
  3. const int inf=0x7fffffff,maxn=;
  4. int G[maxn][maxn],vis[maxn],cost[maxn],n;
  5. //len为MST长度
  6. int prim(){
  7. memset(vis,,sizeof(vis));
  8. //加入起始节点
  9. int pos=,min=inf,len=,cnt=n;
  10. vis[]=;
  11. for(int v=;v<=n;v++)cost[v]=G[pos][v];
  12. //加入剩余n-1个节点
  13. while(--cnt){
  14. for(int i=;i<=n;i++)if(!vis[i]&&cost[i]<min){
  15. pos=i;min=cost[i];
  16. }
  17. len+=min;vis[pos]=;
  18. //以新加入的节点为中心,更新权值信息
  19. for(int i=;i<=n;i++)if(!vis[i]&&G[pos][i]<cost[i])
  20. cost[i]=G[pos][i];
  21. min=inf;
  22. }
  23. return len;
  24. }

结合poj上的一道水题来验证一下Prim的威力吧~亲测156k内存0ms过(C++编译器)

poj1258:http://poj.org/problem?id=1258

Kruskal算法:(基于边的贪心算法)基于边的贪心,由图的性质不难知道,当图为稠密图时,边的数目远大于点的数目,因此Kruskal+并查集适用于稀疏图

  1. 将所有的边按权值由小到大排序——准备工作,可借助sort()完成,但是在工程中,如果不知道边和点的数量关系,还是应该用最小值堆,而不是sort来保证效率,但在竞赛中,sort足够了
  2. 从非MST中的边中寻找一条,在不会与现有的MST构成环的前提下,权值最小的边,加入MST
  3. 如果已经加入了n-1条边,则结束,否则返回步骤2

那么从算法描述,我们不难看到,整个算法中的核心部分是,判断当前权值最小的边是否会与MST构成环。

那么如何实现这个判断呢?一种思路是我们通过BFS或者DFS,用遍历图的办法来判断——然而这个编程复杂度和时间复杂度都很高╮(╯-╰)╭

我们可以从另一个角度进行考量。如果说我们给每个MST一个代表元素(representative),或者说,是一个标记,那么,对于一个不连通的无向图,每个MST就可以看作一个连通支,而每个连通支其实可以看作一个集合,连通支中的节点就是集合中的元素,而我们只关心一个新的元素是否在原先的集合中。

那么判定元素是否在集合中,我们是不是马上想到了一种树形结构——并查集(Union-Find Set)。

并查集的数组实现如下:p[x]表示第x元素的父元素,我们规定当p[x]==x时,表示找到了这一组元素的代表元(representative),

则可以递归的进行查找,并同时进行路径压缩,因此,不难看出,在均摊意义下,并查集的时间复杂度为O(1)。

  1. int find(int x){ return p[x]== x ? x : p[x] = find(p[x]); }

为什么return语句可以这样和赋值语句连用?

大家想想诸如a=b=c=1;这样的连续赋值,不难理解,其实赋值语句是有返回值的,并且返回值为左值的值,即先返回c的值1,赋给b,返回b的值1,赋给a,最后返回a的值。

这样,我们就可以给出kruskal的完整实现了:

  1. const int maxn=;
  2. //n为节点个数,m为边个数,r存储第i+1小的边的序号,w存储第i条边的权值,u和v存储第i条边的节点序号
  3. int p[maxn],n,u[maxn],v[maxn],w[maxn],r[maxn],m;
  4. //并查集find
  5. int find(int x){ return p[x]==x?x:p[x]=find(p[x]); }
  6. //间接排序函数
  7. int cmp(const int i,const int j){ return w[i]<w[j]; }
  8. int kruskal(){
  9. int len=;
  10. for(int i=;i<n;i++)p[i]=i;//初始化并查集
  11. for(int i=;i<m;i++)r[i]=i;//初始化边的序号
  12. sort(r,r+m,cmp);//<algorithm>中的优化的快排
  13. for(int i=;i<m;i++){
  14. int e=r[i],x=find(u[e]),y=find(v[e]);
  15. if(x!=y){ len+=w[e];p[y]=x; }//并查集Union
  16. }
  17. return len;
  18. }

不难看出,Kruskal算法的复杂度为O(ElogE),基本上都集中在排序了,所以,工程上还可以用优先队列或者斐波那契堆来减小复杂度

这样,无向图中的MST模型就介绍的差不多了,通常这个模型会用于解决资源最省之类的问题,不过,kruskal还没有实践过,所以,有时间我再更新一些相关习题吧~

最小生成树(Minimum Spanning Tree)——Prim算法与Kruskal算法+并查集的更多相关文章

  1. 【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解

    本节纲要 什么是图(network) 什么是最小生成树 (minimum spanning tree) 最小生成树的算法 什么是图(network)? 这里的图当然不是我们日常说的图片或者地图.通常情 ...

  2. Prim算法、Kruskal算法和最小生成树 | Minimum Spanning Tree

    graph to tree非常有趣! 距离的度量会极大地影响后续的分析,欧式距离会放大差异,相关性会缩小差异,导致某些细胞群分不开. 先直观看一下,第一个是Prim,第二个是Kruskal.但是肯定都 ...

  3. 最小生成树 (Minimum Spanning Tree,MST) --- Kruskal算法

    本文链接:http://www.cnblogs.com/Ash-ly/p/5409265.html 引导问题: 假设要在N个城市之间建立通信联络网,则连通N个城市只需要N - 1条线路.这时,自然会考 ...

  4. Minimum Spanning Tree.prim/kruskal(并查集)

    开始了最小生成树,以简单应用为例hoj1323,1232(求连通分支数,直接并查集即可) prim(n*n) 一般用于稠密图,而Kruskal(m*log(m))用于系稀疏图 #include< ...

  5. 最小生成树(MST)Prim算法和Kruskal算法

    刚学完最小生成树,赶紧写写学习的心得(其实是怕我自己忘了) 最小生成树概念:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 就是说 ...

  6. 最小生成树 (Minimum Spanning Tree,MST) --- Prim算法

    本文链接:http://www.cnblogs.com/Ash-ly/p/5409904.html 普瑞姆(Prim)算法: 假设N = (V, {E})是连通网,TE是N上最小生成树边的集合,U是是 ...

  7. 算法练习:最小生成树 (Minimum Spanning Tree)

    (注:此贴是为了回答同事提出的一个问题而匆匆写就,算法代码只求得出答案为目的,效率方面还有很大的改进空间) 最小生成树是指对于给定的带权无向图,需要生成一个总权重最小的连通图.其问题描述及算法可以详见 ...

  8. cf609E Minimum Spanning Tree For Each Edge (kruskal+倍增Lca)

    先kruskal求出一个最小生成树,然后对于每条非树边(a,b),从树上找a到b路径上最大的边,来把它替换掉,就是包含这条边的最小生成树 #include<bits/stdc++.h> # ...

  9. 数据结构与算法分析–Minimum Spanning Tree(最小生成树)

    给定一个无向图,如果他的某个子图中,任意两个顶点都能互相连通并且是一棵树,那么这棵树就叫做生成树(spanning tree). 如果边上有权值,那么使得边权和最小的生成树叫做最小生成树(MST,Mi ...

随机推荐

  1. c# IList<T> 深拷贝

    class A { public string a1{get;set}; public string a2{get;set}; public IList<B> a3{get;set}; / ...

  2. 【题解】ZJOI2007报表统计

    洛谷传送门 主要思路大概也是差不多的,对于两种询问分别用线段树与平衡树来维护. 1.MIN_SORT_GAP:显然平衡树简单操作,来一发前驱.后继即可. 2.MIN_GAP:这一个我用的是线段树:可以 ...

  3. 【BZOJ 1409】 Password 数论(扩展欧拉+矩阵快速幂+快速幂)

    读了一下题就会很愉快的发现,这个数列是关于p的幂次的斐波那契数列,很愉快,然后就很愉快的发现可以矩阵快速幂一波,然后再一看数据范围就......然后由于上帝与集合对我的正确启示,我就发现这个东西可以用 ...

  4. [Usaco2005 Dec]Cleaning Shifts 清理牛棚 (DP优化/线段树)

    [Usaco2005 Dec] Cleaning Shifts 清理牛棚 题目描述 Farmer John's cows, pampered since birth, have reached new ...

  5. Out of memory due to hash maps used in map-side aggregation解决办法

    在运行一个group by的sql时,抛出以下错误信息: Task with the most failures(4): -----Task ID:  task_201411191723_723592 ...

  6. hbase vs mongodb

    1.HBase依赖于HDFS,HBase按照列族将数据存储在不同的hdfs文件中:MongoDB直接存储在本地磁盘中,MongoDB不分列,整个文档都存储在一个(或者说一组)文件中 (存储) 2.Mo ...

  7. hbase集群写不进去数据的问题追踪过程

    hbase从集群中有8台regionserver服务器,已稳定运行了5个多月,8月15号,发现集群中4个datanode进程死了,经查原因是内存 outofMemory了(因为这几台机器上部署了spa ...

  8. 解读dbcp自动重连那些事

    转载自:http://agapple.iteye.com/blog/791943 可以后另一篇做对比:http://agapple.iteye.com/blog/772507 同样的内容,不同的描述方 ...

  9. js如何弹出新窗口

    js如何弹出新窗口 时间:2012-4-22 弹出新窗口也是在网页设计中会经常用到的,其用法也很简单,是通过调用javascript的内置函数windows.open来产生的.  window.ope ...

  10. python基础(3)_列表、元组、字典

    一.列表 定义:[ ] 内以逗号分隔,按照索引,存放各种数据类型,每个位置代表一个元素 特性: > 可存放多个值 > 可修改指定索引位置对应的值,可变 > 按照从左到右的顺序定义列表 ...