700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > prim算法_图的生成树之最小生成树(Prim)

prim算法_图的生成树之最小生成树(Prim)

时间:2022-07-13 13:04:51

相关推荐

prim算法_图的生成树之最小生成树(Prim)

树一般来说是基于图而生的,而树是一种特殊的图:无环连通图。

定义1:对于无向图G和一棵树T来说,若T中与G中所包含的节点相同,但是边不相同,且T恰好是无环的连通图,那么称T为G的生成树。

对于图的生成树,主要有无向图的最小生成树、次小生成树和有向图的最小树形图三种问题。这一次我们主要讲比较容易理解的最小生成树。

那什么是最小生成树呢?有以下定义:

定义2:对于一个具有边权的图来说,其边权和最小的生成树称作图G的最小生成树。

图的最小生成树在实际的生活中有很重要的应用,比如说用一个图表示n个城市之间的交通网,边上的权值是公路预算,现在要修建公路将多个城市连起来使得开销最少,这不就是一个求解最小生成树的问题么?

一般来说,对于一个图来说,若图中的边权值各不相同,那么图的最小生成树唯一。这个证明过程这里不再赘述,可以参考算法导论上的相关章节.

求解无向图的最小生成树主要有Prim算法和Kruscal算法两种。比较好理解的是Kruscal算法,所以我们先讲一下Prim算法。

我们现在抛开复杂的算法流程,在Prim没有发现这个算法之前,你能自己去发现么?

首先我们骨子里会有一种贪心的思想,既然是树上最小的边权和,那我希望每一步选择的边权都是最小的。此时我想从图的任意一点出发,选择与出发点相连的所有点中连接边权最小的点,此时已经访问了两个点。然后呢?我们继续找最小的权值,然后我们以第2个访问的点为起点,继续上述的操作?好像有点问题。因为可能会出现下面的这种情况:

我们会错过A-D这条更小的边,说到这里我们就会很清楚了,我们假设的以A为起点,但其实A可能只是最小生成树路径中间的一个点(不是最头上的点),因此在寻找最短边权的时候,我们要以访问过的点集为整体,去找离此点集(即红色圈内部分)最近的点对应的权值,那么什么叫离点集最近呢,下面我写一个公式:

形象一点就是在所有蓝色边中找最小的边:

好了我们重新捋一遍刚才的思路:

1.每次选取 离已访问点集最近 的点,加入集合,把对应的边权加入到最小生成树权值中

2.若找到了n-1条边,则结束算法;否则重复1过程。

为啥找到n-1条边就可以呢,因为一个图若有n个节点,则它的最小生成树必定只有n-1条边,否则再多一条边就会出现环,可以自己画画看。

这个算法就叫Prim算法,是不是感觉我们自己也能想到?我们只是感觉这样做对,但是并没有去证明这么做为什么对。不过现在可以告诉你通过这样的步骤,最后得到的确实是最小生成树。感兴趣的同学还是可以去算法导论去看相关的证明。

这个算法中一个关键的地方是如何维护已访问点集到所有其他未访问点的距离,我们可以用一个d[]数组来进行维护每一个点(未访问的点)到已访问点集的最短距离,这样每次我们访问一个点之后,对d[]数组进行更新,然后再访问其他的点重复此过程。接下来,我们通过以下的图来模拟一下过程。

所有的过程结束之后,我们来用代码一步一步的实现了。

初始化要用到的数组:Map[ ][ ]用来储存节点之间的权值,vis[ ]标记是否被访问,pre[ ]记录前驱节点,d[ ]维护到已访问点集的最短距离。

const

2. 初始化

memset

3. 我们先从第一个点开始访问,这里我们从1节点开始:

d

因为此时集合中只有一个节点1,因此自己到自己的最短距离为0。

4. 关键代码

for

进行n-1次循环是因为我们每一次操作都会往已选点集中加入一个新的点(也可以理解为加上一条新的边),而含有n个节点的图的最小生成树的边数必定n-1,因此只要n-1循环即可。

代码中比较关键的 一个地方是松弛的过程,要明白和理解松弛的目的是什么?我们以下图为例说明一下。

当已访问点集中只有A的时候,未访问节点C到已访问点集{A}的最短距离为d[C]=3,当我们往集合中加入一个新的节点B时,我们会发现,由于点B 的加入,使得已访问点集{A,B}与点C的连接关系发生了变化,产生了新的连接关系:B-C。由于此图是一个平面图(二维),因此每加入一个新的节点,有且只有一个新的连接边产生,而对于点C到已访问点集最短距离的更新,一定是在新产生的连接边B-C和原来的d[C]中选择一个较短的进行更新,即:

对每一个点都执行上面的操作,这个更新的操作就叫做松弛。对每一个未访问节点松弛后,每一轮最后就可以得到所有未访问节点各自到已访问点集的最短距离。下一轮(下一次循环)的开始,再从这些最短距离里选择一个最短的,将连接的点加入已访问集合,以此类推。

这样我们就得到了最小生成树,权值也可以计算出来:在程序中 每加入一个节点,就把此节点与上一个节点的边权加入最终的最小生成树权值中即可。

讲完这个Prim算法,我们想一下 是不是可以优化呢?

我们发现在求最短距离的时候,要遍历所有连接的邻近点才能求出最小的距离。如下:

for

这里我们可以用一个优先队列priority_queue来对这个最小值进行维护,每次从队首取出最小值,优先队列内部是用堆实现,因此复杂度也会降低。大家可以自己实现一下,比较简单。

Prim算法就讲到这里,下一次我会继续讲解另一种最生成树算法Kruscal。

点个赞再走吧!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。