求无向连通图的最小生成树算法——Prim与Kruska

http://hi.baidu.com/%CA%FD%BE%DD%BD%E1%B9%B9%BF%CE%B3%CC/blog/item/fd0dd984f3b99126c75cc39c.html

求无向连通图的最小生成树算法——Prim与Kruskal及相关优化

数据结构课程

最小生成树是图论里很重要的部分。但是由于它属于图论所以NOIP基本不考,对于NOI又太基础,所以竞赛中出现的几率比较小,即使要考也不可能考裸的生成树算法= =

最小生成树就Prim和Kruskal两个算法,又没有多大的优化余地,所以学习起来还是很简单的。

    

 

 

一.Prim算法

     

 

1.算法思想

 

    对于图G=(V,E),用Prim算法求最小生成树T=(S,TE)的流程如下

   ① 初始化:设S、TE为空集,任选节点K加入S。

    ② 选取一条权值最小的边(X,Y),其中X∈S,且not (Y∈S)即,选取一条权值最小的、连接着S中一点与S外一点的边。

       将Y加入S中,边(X,Y)加入TE中

    重复② 直到V=S即所有G中的点都在S中,此时的T为G的最小生成树。

 

    由此流程可见,Prim算法求最小生成树时任何时候的T都是一颗树。

        

 

 

2.实现

                        

 

       显然,Prim算法的主要运行时间花在过程②的选边中。看起来复杂度是O(VE)=O(V^3)不是么,效率也太低了吧……

                                     

 

       为了比较快速地选边,我们用两个数组lowcost、closest动态地维护每一个点到S的最短距离。在某一状态下,lowcost[i]表示所有与i相连且另一端点在S中的边中的权值最小值,closest[i]表示在S中且与i相连的点中与i之间距离最小的点。显然,lowcost[i]=w(i,closest[i])。需要注意的是两个数组记录的都是而不是路径。若i没有边直接连向S,则lowcost[i]=∞。另外,若i已在S中,则lowcost[i]=0。

  

       设出发点为x。初始时对于任意k∈V,closest[k]=x,lowcost[k]=w(k,x)【w(i,j)表示i、j间的距离。初始化时,若两点间没有边则w(i,j)赋为一个足够大的整数(如maxint),并且所有点到自身的距离赋为0,即w(i,i)=0】

    

       每一次找出lowcost中不为0的最小值lowcost[i],然后把i加入S(即lowcost[i]:=0),然后对于图中所有点k,若w(k,i)<lowcost[k],则把lowcost[k]赋为w(k,i),把closest[k]赋为i。【由于S中所有点的lowcost都为0,所以只影响到S以外的点】

  

 

以上操作重复|V|-1次结束。由于每次加入S的点i都在当时取到了符合流程②的边min{lowcost},而lowcost[i]=w(i,closest[i]),所以此时的最小生成树的各边就是(i,closest[i]),i∈V且not (i=x)【需要注意的是出发点x的closest[x]还是x,所以应忽略,实际取到x-1条边】。把i从1取到|V|,便得到最小生成树T的每条边。

         

 

 

Pascal程序:(摘自NOCOW,略有改动)

(设边权存于数组cost,图的顶点个数|V|为n)

 


 

 

 

不难看出,以上算法包含一个二重循环,算法复杂度为O(V^2),与图的稠密度无关。

 

         

 

 

 

3.使用堆优化

       

    如果我们把要取的边作为一个最小优先级队列,用堆来选取边与调整,则算法可以得到优化。

   

对于上面代码绿色部分,转换成堆后就是一个建立一个有V个节点的小根堆的过程,复杂度为O(V);对于红色部分,就是取堆顶元素【O(1)】并删除、维护【O(logV)】;对于橙色部分,对于for循环我们可以用一个数组edge[i]记录与i节点相连的所有边,这样我们就把for循环的复杂度变成了与E相关。显然,图中的每条边仅在其两顶点进入最小生成树时被访问一次,也就是每条边被访问2次,因此橙色部分的执行次数总共是2E次而与外面的while无关。里面的lowcost修改时需要维护堆,复杂度为O(logV)。因此,算法的总复杂度为O(VlogV+ElogV)=O(ElogV)。具体程序见本文最下方。

   

对于|E|接近|V|的稀疏图,算法复杂度接近O(VlogV)显然比O(V^2)的算法优秀很多。然而对于E接近N^2的稠密图,算法复杂度接近O(V^2logV)反而不如平方算法。因此,使用堆优化的Prim算法适用于稀疏图,而不优化的Prim算法适用于稠密图。不过O(V^2logV)实际上不比O(V^2)慢多少,而现在的题目大多是稀疏图,所以这个算法在总体上是优于朴素Prim算法的。

   

 

顺便一提,如果用斐波那契堆来做的话,时间复杂度可以进一步优化为O(E+VlogV)。但事实上这样做的代码长度会到令人发指的地步,所以是一种只存在于论文的算法。应付一般的竞赛用堆优化的Prim就可以应付所有数据了。

            

 

 

 

二.Kruskal算法

        

 

1.算法思想


    设最小生成树为T=(V,TE),初始时设TE为空集。将G中所有边按权值排序,从小到大选取边。若当前选取的边加入TE后不会使TE出现环,则将边加入TE,否则舍弃之。当TE中有n-1条边时,算法结束。此时T=(V,TE)为G的最小生成树。

   

关于判断是否会产生环,有一个简单的方法:若i与j中已有一条路径i-k1-k2-…-kn-j,若加入边(i,j)就会形成环【i-k1-k2-…-kn-j-i】。因此,若一条边连接两个连通分量就不会形成环,反之则会形成环。初始时每一个点就是一个连通分量,当加入(i,j)时,判断i与j是否属于不同连通分量。如果是的话,在加入边后把i与j所在的连通分量合并。重复以上操作,就可以保证不会产生环。

          

 

 

2.实现

 

 

显然,Kruskal的难点在于判断、合并连通分量。Pascal语言中已有集合类型可以实现这一操作,不过竞赛中set是一个极其危险的类型【基本常识】。此处只用到查询、合并两个操作,是一个很明显的并查集模型,每一个连通分量就是并查集里的一个集合,通过并查集的算法即可实现算法。

   

 

3.时间复杂度分析

  

对E条边排序的复杂度为O(ElogE);E次并查集的查找、合并的复杂度也约为O(ElogE),因此算法的复杂度为O(ElogE)。又由于|E|的取值范围为【|V|-1】~【|V|(|V|-1)/2】=【|V|】~【|V|^2】,所以log|E|的范围是【log|V|】~【2log|V|】。因此O(logE)可以写作O(logV)。为了方便与Prim算法复杂度的比较,一般来说把Kruskal的复杂度表述为O(ElogV)

         

 

 

 

三、Prim、Kruskal、Prim+Heap算法效率实测

   

评测环境:WindowsXP,FreePascal2.40,Pentium(R) Dual-Core CPU T4300@2.10GHz,2G内存

(转)求无向连通图的最小生成树算法鈥斺擯rim与Kruska

(转)求无向连通图的最小生成树算法鈥斺擯rim与Kruska

(转)求无向连通图的最小生成树算法鈥斺擯rim与Kruska

(转)求无向连通图的最小生成树算法鈥斺擯rim与Kruska

(转)求无向连通图的最小生成树算法鈥斺擯rim与Kruska


 

 

通过上图可以看出:

1.Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。

2.Prim+Heap在任何时候都有令人满意的的时间复杂度,但是代价是空间消耗极大。【以及代码很复杂>_<】

3.时间复杂度并不能反映出一个算法的实际优劣。

 

 

竞赛所给的题大多数是稀疏图,所以尽可能地使用Prim+Heap吧,在稀疏图中这是无敌的。如果一定要在朴素Prim和Kruskal里选一个的话那就用Kruskal吧。当然Prim的代码比较简单,对付水题用Prim也无所谓,只要不是极稀疏图两者相差不大。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Kruskal算法Prim算法都可以解任何一个带权无向连通的最小生成。其中,Kruskal算法基于贪心思想,通过不断选择边权值最小且不会形成环的边来构建最小生成;而Prim算法则是从一个起点开始,每次选择与当前生成相邻且权值最小的边加入生成中,直到生成包含所有节点为止。两种算法的时间复杂度均为O(ElogE),其中E为边数。 ### 回答2: 任何一个带权无向连通的最小生成是指在该中找到一棵包含所有节点的生成,使得该生成的边权之和最小。 为了更好地理解最小生成,我们可以以一个具体的例子来说明。假设有如下一张无向: ![image.png](https://cdn.luogu.com.cn/upload/pic/24255.png) 这张中有6个节点和7条边。如果我们需要在该中找到一棵包含所有节点的生成,则可以得到如下几个解: - 选择边(1, 2)、(2, 3)、(2, 4)、(3, 5)、(4, 6),生成的边权之和为7+2+4+5+1=19 - 选择边(1, 2)、(2, 3)、(2, 4)、(4, 5)、(6, 4),生成的边权之和为7+2+4+3+1=17 - 选择边(1, 2)、(2, 4)、(3, 5)、(4, 5)、(4, 6),生成的边权之和为7+4+5+3+1=20 可以发现,虽然以上三个解都是包含所有节点的生成,但其边权之和是不同的。其中,第二个解的边权之和最小,可以称其为该的最小生成。 从上述例子中可以看出,在寻找最小生成时,我们需要在生成中选择边的过程中,不断地计算边权之和,同时确保所生成包含中所有的节点。这种算法中常用到的是Kruskal算法Prim算法Kruskal算法依据的是贪心策略,每次选择边权最小并且不与已选择的边构成环的边,依次将这些边加入生成中。最终的生成即为最小生成Prim算法也是一种贪心算法,其选择边的方式与Kruskal算法不同。Prim算法从任一节点出发,每次将与当前生成距离最短的未选择的节点连接起来,逐步扩大生成的范围,直到所有节点都被包含在生成中。最终的生成即为最小生成。 总之,最小生成是一个经典的论问题,在实际应用中具有广泛的价值和意义。了解并掌握相应的算法,可以有效地解决实际问题,提高数据处理的效率。 ### 回答3: 最小生成,也被称为MST(Minimum Spanning Tree),是指在一张带权中,将所有节点彼此连接起来且总权值最小的。在实际应用中,最小生成可以帮助我们寻找最优的物流路径、路网系统等问题。 任何一个带权无向连通的最小生成,可以使用Prim算法Kruskal算法来计算。这两种算法都是贪心算法,用来选择权值最小的边来构建最小生成Prim算法基于节点,从一个固定的起点开始构建最小生成,每次在当前生成中找到最近的未加入节点,然后加入这个节点到当前中去。Prim算法通过建立一个优先队列,不断地选取权值最小的边来构建最小生成Kruskal算法基于边,将所有边按照权值从小到大排序,每次选择一条没有形成环的边加入生成中。如果新加入一条边会形成环,则不加入这条边,并选择一条权值更小的边。Kruskal算法通过并查集来判断是否产生环,并在遍历完所有边之后得到最小生成。 需要注意的是,如果带权不是连通,那么最小生成就不存在。如果要处理非连通,可以先把进行连通分量的划分,然后再对每个连通分量分别最小生成。 总之,无论是Prim算法还是Kruskal算法,对于任何一个带权无向连通,都可以用贪心算法出最小生成。这样的最小生成可以帮助我们寻找最优的路径,优化网络,使得节点之间连接更加紧密,大大提高了系统的可靠性和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值