最小生成树的本质是什么?Prim算法道破天机

本文介绍了Prim算法的核心思想——点集的拓张,通过对比Kruskal算法,阐述了Prim算法如何从点的角度构建最小生成树。在Prim算法中,边的选择受限于已覆盖和未覆盖的点,使用优先队列来维护可增广边的顺序,从而实现贪心策略找到最小生成树。最后,提供了算法的Python实现和邻接表的数据结构说明。
摘要由CSDN通过智能技术生成

本文始发于个人公众号:TechFlow,原创不易,求个关注


今天是算法和数据结构专题20篇文章,我们继续最小生成树算法,来把它说完。

在上一篇文章当中,我们主要学习了最小生成树的Kruskal算法。今天我们来学习一下Prim算法,来从另一个角度来理解一下这个问题。


从边到点


我们简单回顾一下Kruskal算法的原理,虽然上篇文章当中用了很多篇幅,但是原理非常简单。本质上就是我们对图中所有的边按照长度进行排序,之后我们按照顺序依次把它作为树的骨干,加入到树上来。

在此过程当中,我们为了避免导致产生环,而破坏树结构,所以使用了并查集算法来作为维护。只会考虑那些不在一个连通块中的边,否则就会构成环路。

很多人在学习了这个算法之后,会将它理解成贪心问题,或者是并查集的一个使用场景。这么理解倒也没错,但是在这个问题当中,还有更好的解释。

这个解释就是边集的扩张

整个Kruskal运行的过程是我们不断选择边加入树中的过程,对于n个点的图来说,我们需要n-1条边。如果我们专注于这个被选出来边的集合,那么算法开始的时候,它是空集,运行结束之后,它含有n-1条边,达到饱和。

当然你也可以换个角度来看,如果我们的关注点在点上,那么最小生成树构建的过程也同样可以看成是点集的拓张。只不过点和边不同,边可以选择,但是点不可以,点只能通过选择边来覆盖。比如我们看下下图:

在图中,我们左边是一棵已经构成的树,当我们连通AE之后,我们就用边覆盖了E点。点是依托于边的,不通过边,是无法覆盖点的。

我们从空集开始,除了第一条边可以覆盖两个点之外,之后的每一条边都连通一个已经覆盖的点和一个没有覆盖的点。那么,同样也是通过n-1条边可以覆盖n个点。这个就是Prim算法的核心思路,也就是点集的拓张。


整体思路


我们明白了Prim的核心思想是点集的拓张之后就容易了,由于我们每次选择的边两边一定是一个已经覆盖的点和没有覆盖的点。所以我们生成的树是一条边一条边逐渐长大的,而不是像Kruskal那样东拼西凑起来的。

我们来看个例子:

我们已经连通了ABCD四个点,其中CE的长度是7,DF的长度是9,EF的长度是5。虽然EF的长度小于CE,但是由于我们必须要连通一个已经覆盖的点和没有覆盖的点,虽然EF的距离更小,我们也不能选择。只能选择CE,所以在整个算法运行的过程中间,这棵树是逐渐变大的。如果是Kruskal,我们肯定会先连通EF,再连通CE,整个算法运行的过程当中,各个部分都是隔开的,最后的树其实是逐渐“拼凑”出来的。

和Kruskal维护集合相比,我们维护点有没有覆盖过则要容易得多。因为树已经选择的边是不会修改的,所以我们只需要用一个数组标记一下每个位置的点有没有覆盖即可。简单的bool类型就可以实现,非常方便。

所以我们的问题只剩下了一个,如何保证我们生成出来的树的路径和最小呢?

关于这个问题的回答Prim和Kruskal一样,就是贪心。我们每次选择最小的边进行拓展,Kruskal是对所有边进行排序,然后依次判断能否选择。那么Prim算法怎么用贪心呢?

其实也很简单,我们也很容易想明白。Prim算法对边有限制,只能选择已经覆盖的点和没有覆盖的点之间的连边。我们给这些边起个名字,叫做可增广边。那么,显然我们要做的就是在可增广边当中选择一条最短的进行增广。

问题就只剩下了一个,我们怎么选择和维护这个最短的可增广边呢,难道每次拓充之后,都进行排序吗?

显然不是,因为每次都排序带来的开销太大了,我们可以用一个数据结构来维护这些边,让它们按照边的长度进行排序。这个数据结构我们应该很熟悉了,就是我们已经遇见过好几次的——优先队列

我们排序的键也已经很明显了,就是边的长度,边是否合法的判断也很简单,我们只要判断一下是否存在没有覆盖的点即可。于是整个流程就串起来了,我们可以先来把流程理一下,写出它的流程:

选择一个点u,当做已经覆盖
把u所有相连的边加入队列
循环
    循环 从队列头部弹出边
        如果边合法
            弹出
            跳出循环
    获取边的两个端点
    将未覆盖的端点所有边加入队列
直到所有点都已经覆盖

最后,我们看下Python的实现,首先是优先队列的部分,这个逻辑我们可以利用现成的heapq来实现。

import heapq

class 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值