最小支撑树


这篇介绍的是最小支撑树,常见的有Prim算法和Krustal算法。

支撑树连通图G的某一无环连通子图T覆盖G中所有的顶点,则称作G的一颗支撑树或生成树(spanning tree)。

支撑树必须覆盖所有的顶点,并且不能有环路,因此是禁止环路前提下的极大子图,也是保持通路前提下的最小子图。一个图可能有很多支撑树,它们都包含n个顶点和n-1条边。

最小支撑树:在带权网络G所有的支撑树中,成本最低的称为最小支撑树(MST)。

Prim算法

割(cut):在图G=(V;E)中,顶点集V的任一平凡子集U及其补集V\U构成一个割。如果边uv满足u属于U且v不属于U,称作是该割的一条跨越边(cross)。跨边联接于V及其补集之间,也称作该割的一座桥。

Prim算法迭代的准则:最小支撑树总是会采用联接每一割的最短跨越边。

算法的策略是贪心迭代:从一个点出发,每次都寻找已经得到的支撑树子树T'与剩余的点构成的割的最短跨越边,并把它加入支撑树。算法复杂度为O(n^2),适合于稠密图、

考虑算法的过程,我们可以在发现一个顶点时,把它的优先级设置为与子树T’的联边的权重。实际上,也就是把顶点的优先级设置为跨越边的权重,然后寻找那个最短的跨越边,加入到子树中。因此,这个问题也可以归于优先级搜索的框架:

 1 template<typename Tv, typename Te> struct PrimPU
 2 {
 3     virtual void operator()(Graph<Tv, Te>* g, int uk, int v)
 4     {
 5         if (g->status(v) == UNDISCOVERED)//对于uk每个尚未被发现的邻接顶点v
 6             if (g->priority(v) > g->weight(uk, v))
 7             {
 8                 g->priority(v) = g->weight(uk, v);//更新优先级数
 9                 g->parent(v) = uk;//更新父节点
10             }
11     }
12 };

只需要重写优先级更新器即可。

Krustal算法

前面的prim算法是以每一割的最短跨越边来扩展支撑树,通过更新点的优先级可以选取点和树边。

如果不考虑动态边的情况,而是直接选择边的长度,似乎可以更加直观一点。这也是krustal算法的思想。

把图中所有的边,按照权重进行排序,然后从最短的边开始,把边和它的两个端点u和v加入到支撑树中。然后按照边的顺序,继续选择最短边,检查两个端点是否已经在支撑树中,如果两个点都在,那么舍弃这条边继续进行;只有一个端点在,把这条边和新增的顶点加入支撑树,此时可能会是两个子树的合并操作;两个端点都不在,成为一个新的子树。知道已经有n-1条边时算法结束。

krustal算法在某些时候的效率可能会很高,但有时效率可能会很低,需要检查完所有的边才能结束算法,比如其他点之间的边权重很小,而某一点只与很少的点有联边且联边权重很大的情况。

另外,krustal算法要求图可以提供两种基本操作:一种是并,即把两个支撑树子树合并起来;另一个是查,检查一个顶点是否在子树中。支持这样的操作,称为并查集。

算法时间复杂度为O(eloge)。复杂度与边的数量有关,因此,该算法适合边比较稀疏的图。

我没有想到能把krustal纳入到优先级搜索框架中的方法,因为krustal算法需要实现并查集的操作。可以考虑用这样的结构来实现:把所有的顶点放在一个向量结构中,子集用多叉树表示;当采用一条边的时候,更新一个顶点的parent指针;在选择边的过程中,需要在各个子树中进行查找操作;合并的操作即相当于两棵树的合并;最终的结果必然是得到了一个顶点作为根节点的多叉树结构。

总结来看,两种算法的最大不同之处,在于krustal对边采取了排序操作,而prim算法没有进行预处理。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM