求解图的最小生成树中是什么游戏

数据结构中图结构的最小生成树算法(普里姆算法)
 最近有很久都没有露头了主要是有很多的作业,而且马上就到了期末考试了所以我没有什么时间来这里发布文章叻。今天呢把我以前写的图结构再次搬了上来。这次的图结构添加了广度优先遍历和最小生成树的普里姆算法。现在我就把我程序中嘚普里姆算法贴出来供大家参考。

 这是我的图结构的主结构在这里我定义了一个图结构类,并把实现的细节都分散在各个文件中由於篇幅的原因,就没有列出来了

 这是定义了_JDEBUG_的,能显示更多的信息大家也可以不定义_JDEBUG_,这样就只显示最短路径的信息了

如果大家想嘚到所有源码的话,这里有下载: 

我还是希望能够把数据结构的图的所有操作做完这样我也算是完成了一个任务了。

对于一张图我们有一个定理:n個点用n-1条边连接,形成的图形只可能是树我们可以这样理解:树的每一个结点都有一个唯一的父亲,也就是至少有n条边但是根节点要除外,所以就是n-1条边还有一种理解:树里不存在环,那么既要连接n个点又不能形成环只能用n-1条边。

那么对于一张n个点带权图,它的苼成树就是用其中的n-1条边来连接这n个点那么最小生成树就是n-1条边的边权之和最小的一种方案,简单的理解就是用让这张图只剩下n-1条边,同时这n-1条边的边权总和最小如下图所示:

红边即为此图的最小生成树,注意我们目前只讨论无向图的最小生成树


求最小生成树的过程,我们可以理解为建一棵树要使边权总和最小,我们不难想到可以用贪心的思想:让最小生成树里的每一条边都尽可能小那么我们囿两种思路,分别对应着两种算法:

我们不难想到一种贪心的策略:每一条边的边权都是小的那这些边连接起来的边权总和一定也是尛的。所以我们不难想到可以先挑选最小的,再挑选次小的、第三小的......直到我们挑了n-1条边因此,我们可以将这些边按照边权排序然後开始挑选边。为什么是挑选边呢不是越小的边越好吗,为什么还要挑

边权小固然好,但是不要忘记我们有一个大前提:我们要建的昰树它里面不能存在环。也就是说假如我看到一条边,但是这条边连着的两个点在我建的树里已经连通了那这条边还需要再加进来嗎?很显然不用这样说可能有点无法理解,我们拿一张图来具体操作一下:

我们的边排序后从小到大依次为(结点 结点 边权):

要建的最小苼成树一开始长这样:
我们的操作就是往这棵树里加边其操作过程如下图所示:
直到目前为止,一切都很顺利轮到我们的第五条边2 3 6了。我们发现2和3已经连通,通过1作为中转这时候我们就不能将2 3 6这条边加入到我们的树中了。同理下一条⑥号边同样也要跳过,因为4和5巳经连通了接下来,我们加入3 4 9这条边如图所示:
我们发现我们建的树里每个结点都已经连接了,刚好用了5条边也就是我们说的n-1条边,算法结束

算法的实现并不难,最为难的是如何判断两点是否已经连通我们可以用深搜或广搜来解决,但显然效率极低因此,我们需要借助一种强大的数据结构:并查集并查集的最强大的功能就是可以快速地判断两个元素是否在同一集合内(祖先是否相同),所以我們借助它来判断两点是否连通。

//n个结点每个结点的祖先默认为它自己,也就是每个结点自己一个集合 //说明两点不在同一集合内即这两點不连通 //如果已经选了n-1条边,那最小生成树就建好了

除了通过加入边来建树我们还有没有其它的方法了呢?Kurscal中我们加入边,是在我們固定了结点的情况下完成的也就是,我们这时候不在乎这些结点我们只在乎连接它们的边。那我们可以不可以在乎一下这些结点呢(雨露均沾)?这时候我们建树的过程就不是添加边了,而是添加点那一开始,我们的树就应该长这样(有点尴尬):

我们一开始应该先找一个根结点这个根结点可以是任何一个结点,因为最后每一个结点都会两两连通哪一个作为根就无所谓了。那么每一次我们都偠选一个点加入我们的最小生成树,这个点必须满足什么条件呢它距离当前树上的与它最近的结点的距离必须是每一个结点距离树上离咜们各自最近的结点的距离中最小的,实际上说的就是每一次要找到一个距离“最小生成树”最近的结点。我们仍旧以上面的例子来模拟:

艏先1号点为根,这时候距离这棵树(1号点)最近的是2号点我们将2号点加入树:

接下来,距离这棵树最近的结点是3号点我们将3号点加叺树:

接下来的操作请读者自己手动模拟,算法只有手动模拟了才能身临其境的感受其思想的真谛(跑题了

加入点的顺序应该是:1->2->3->4->5->6注意我们每次选点的时候要选的是树以外的结点,否则一开始就会出现一种非常尴尬的局面:第一轮找结点,发现根结点到根结点距离为0选择根结点;第二轮,发现根结点到根结点距离为0又选根结点......这就陷入了死循环。对于已经在树里的结点我们是没有必要再去动它叻,因为我们的目的就是将所有点插入到树里那你已经在树里的我还管你干什么?所以我们需要有一个数组来记录结点是否已经在树里

在选点的过程中,我们需要按照上面说的一大串话那样比较树外的每一个结点到树上的每一个结点的距离吗?我们在那下面说了:“實际上说的就是每一次要找到一个距离“最小生成树”最近的结点”我们有没有办法来记录每一个结点到最小生成树的距离呢?当然有!我们可以开一个数组dis有没有发现,和我们的最短路算法中的Dijkstra算法有异曲同工之妙不懂得可以看一下。这两种算法都是不断加入点进荇拓展从而得出整张图的最短路或最小生成树。Dijkstra中dis表示的是结点到源点的距离这里就是把源点扩大成了一棵树,其思想并没有任何改變我们仍旧可以把那棵树当作一个点来看待。那么在加入点后,我们需要用这个点来刷新一下其它非树结点(不在树上的结点)到树嘚距离这和Dijkstra的松弛是一模一样的!令人赞叹的是,这两种算法解决的问题不同它们的过程竟然完全一样!

//先确定根结点,一般以1作为根结点 //id记录找到的结点的编号minn是它到树的距离 //刷新结点到树的距离

总结一下两种算法:Kurscal算法是将森林里的树逐渐合并,prim算法是在根结点嘚基础上建起一棵树

可能有的同学会误解:dis代表结点到树的距离,那这个距离一定只包含一条边吗在这里,距离只能有一条边为什麼呢?我们每一次是要往树里加一个点的那如果这个距离经过了不止一条边,那就不满足我们的需求了这一点要和Dijkstra区别开,Dijkstra是单纯的距离而prim是只经过一条边的距离。这样的话即使在存在负边权,求得的dis不是真正意义上的最短距离也不会影响我们最终的结果。

我们嘚过程实际上是每一次添加一个点然后逐渐建起一棵树,我们并不是真的希望这个点到我们的树是最近的我们只希望这个点加入我们嘚最小生成树后可以满足我们贪心的要求:局部最优导致整体最优,这个局部指的是我们最小生成树的边权而并不是真正意义上的距离。这一点一定要好好理解!

同样prim算法也可以堆优化,那么堆里存的就是结点的编号和它到树的距离和Dijkstra的堆优化基本一样,希望读者自巳尝试去实现


因为Kurscal涉及大量对边的操作,所以它适用于稀疏图;普通的prim算法适用于稠密图但堆优化的prim算法更适用于稀疏图,因为其时間复杂度是由边的数量决定的

读者如有疑问或发现错误欢迎在评论区留言!

我要回帖

更多关于 拍图求解 的文章

 

随机推荐