请注意,该讲解不提供代码模板。
模板题目请走这里
也算是傻愣愣地熟悉一下两种不同的思路了吧。
首先,不得不说Prim算法以及Kruskal算法确实是两个十分优秀的算法。
它们分别从点和边的角度出发建立一个最小的权值树。
在实现过程上,也有诸多注意点要说~~
1、说一说Prim算法吧
Prim算法的核心在于分割开了已经进入生成树的结点集合和还未进入生成树的结点集合两类。
类似图的二分过程,我们寻找的每一条边必须是跨越这两个集合的边,并且每次能够使用的一定还是一个权值最小的边。
我在模板题提供的代码模板
中由lowc数组协同vis数组一同实现了上述核心步骤。
已经进入生成树的结点被vis标记,还未进入生成树的结点没有被vis标记。
如果vis[p]没有被标记&&lowc[p]的值最小,那么p就可以作为生成树的新结点加入生成树。
对于每一个新加入生成树的结点p而言,lowc数组都需要根据v的邻接边的权值进行更新。
之后,lowc数组中一定存在一个边权值,是当前状态下生成树结点集合与非生成树结点集合间的相连边e。
(当然,如果e不存在,那么这张图本身就无法生成一个最小生成树了.....)
随着lowc与vis的配合,生成树中的每一个边的权值以及它对应的一个端点也都会被安全地保存在lowc中。
上面这一段的叙述,基本上就是Prim算法在解决最小生成树时的核心思路了。
上课补充:以点为重心,适合稠密图
2、Kruskal算法
这个算法其实就是离散数学教材上所说的避圈法:将所有边按照权值从小到大依次递增排列,
然后从权值小的边开始逐一选入生成树中,同时要避免后加入的边与前面的边形成圈。
在具体实现过程中,"避免形成圈"依然采用了Prim中的思想,借助vis数组检验即可实现:
因为,形成圈的一定是当前待选边的两个端点u,v都被vis数组标记了。
也就是说,如果vis[u]被标记 && vis[v]被标记那么边e(u,v)一定不会进入生成树。
上课补充:以边为重心,适合稀疏图
3、如何对已经进入生成树的结点和还未进入生成树的结点进行标记?
一种最简单暴力的方法就是vis数组标记。但是我并不推荐这种方法。
我推荐使用并查集的方式来维护。如果find(x)!=find(y),
那么就可以将(x,y)边加入到现有的最小生成树中。
而且,因为在Kruskal算法中有提到避免生成环,
而并查集就是一种常见的用于检测图中是否有环的数据结构。
如果find(x)==find(y)我们认为边(x,y)的加入会产生环。
一旦树中出现环,Kruskal算法将会陷入死循环。
这一点可以通过POJ-2421题目体现出来。
OK