带权无向连通图的最小生成树

最小生成树

930d5776-5a00-49f1-bcda-44b9a22e0de4

网络是边有权重的无向连通图,最小生成树(MST)通常指无负权边的网络中,总边权和最小的生成树。MST有许多实际应用,如“已知任意城市间的距离,用总距离最短的公路连通所有城市”、“用最少长度的导线短接电路板的若干个点”。求MST的一般算法是Kruskal算法与Prim算法,其都为典型的贪心算法。给出几个较直观的引理:

1)包含网络全部顶点的无环连通图是该图的生成树

2)生成树的边数是顶点数-1。

3)生成树添加任意一条边后必然形成环。

4)若一个网络仅包含一个环,删除环上任意一边可得该网络的生成树。

 

Kruskal算法(并查集优化)

算法逻辑流程如下:

1)初始化含网络G所有顶点,但无边的子图G’,此时顶点就是连通分量。

2)从G中找权值最小(先把边权排序记录),且可连接G’某两个连通分量的边,将该边取出加入G’。

3)循环上述2步骤,直到G’只含一个连通分量时,算法结束。

算法逻辑流程的正确性证明如下:
1)首先对于上述Kruskal算法流程,得到的显然是生成树,需要证明的是该生成树是否是最小生成树。
2)把求出的生成树计作T,取图的一个最小生成树M。假设T!=M,则T必含一条以上的边不属于M,取Kruskal算法执行时加入T的第一条不属于M的边计作e。
3)若把e加入M,则必在M形成环,且可在环中找到一个不属于T的边f(因为形成了环)。然后研究e与f的权重关系。
4)若weight(e)>weight(f),说明f在算法某一步被跳过,此时加入f不能连通两个连通分量(等于会形成环)。e是算法加入T的第一条不属于M的边,所以在e边加入前T和M相同,由于f在M中,但M没有形成环,所以导致矛盾。
5)若weight(f)>weight(e),此时删去环中的f,可以得到比M更小的生成树,与M是最小生成树矛盾。
6)故weight(f)=weight(e),此时删去环中的f,可得最小生成树M’,这是用T的第一条不属于M的边替换M的一条边得到的最小生成树,重复该步骤可用T所有不属于M的边替换M的边,最终得到的也是最小生成树,说明T是最小生成树。

利用并查集实现该算法时,其实际流程如下:

1)初始化并查集,其中每个图顶点被初始化为每个单元素动态集;

2)把图边递增排序为序列;

3)从左到右遍历图边序列,若当前边两顶点属于不同动态集,合并两动态集,并把该边标记为MST边;

用并查集是因为对“如何判断边是否连接当前\(G’\)的两个连通分量”等操作有不同代价的实现,由于并查集每种运算的代价接近\(O(1)\),故算法代价为边排序\(O(|E|lg|E|)\)。任何无向图满足\(|E| \leq |V|^2\),故代价可计为\(O(|E|lg|V|)\)。由于连通图的边集含全部顶点,故直接用“边列表”表示图与求得的最小生成树。

 

Prim算法(堆优化)

算法逻辑流程如下:

1)初始化含网络G任意一个顶点,但无边的子图G’。

2)从G中找权值最小(先把边权排序记录),且一端顶点在G’另一端不在G’的边。将该边/顶点取出加入G’(若此时有多组满足条件的边/顶点,任取一组即可)

3)循环上述2步骤,直到G’与G顶点集相同,算法结束。

算法逻辑流程的正确性证明如下:
1)首先对于上述Prim算法流程,得到的显然是生成树,需要证明的是该生成树是否是最小生成树。
2)把求出的生成树先计作T,取图的一个最小生成树M。假设T!=M,则T必含一条以上的边不属于M,任取一条边e。
3)若把e加入M,则必在M形成环,且可在环中找到一个不属于T的边f(因为形成了环)。然后研究e与f的权重关系。
4)若weight(f)<weight(e),由于e/f分别不属于M/T,根据引理(4)若此时对T加入f删去e,可以得到另外一个权重小于T的生成树,说明Prim算法在某次加边时,有权值更小的边可选,这导致与算法步骤矛盾。
5)若weight(f)>weight(e),此时删去环中的f,根据引理(4)可以得到比M更小的生成树,与M是最小生成树矛盾。
6)故weight(f)=weight(e),此时删去环中的f,可得最小生成树M’,这是用T的一条边替换M的一条边得到的最小生成树,由于e边的选择是任意的,重复该步骤可以把M中所有与T不同的边替换掉,最终说明T是最小生成树。

利用小根堆(二叉堆)实现该算法时,其实际流程如下:

1)把所有顶点按索引为\(\infty\)建成\(H\)小根堆(二叉堆);

2)从\(H\)任选顶点将其索引\(decrease\)为0,准备作为图\(G’\)中的所谓初始顶点;

3)从\(H\)中出堆顶点\(u\)并遍历其邻接顶点\(v\),若\(u\)的索引大于边权\(w(u,v)\),则将其索引\(decrease\)为\(w(u,v)\),循环该步骤直到\(H\)为空。对于第1次循环是出堆图\(G’\)的初始顶点,从第2次循环开始,每次出堆的顶点是“可到达目前已出堆顶点的最小权重邻接顶点”,注意到如果记录顶点“最后1次索引\(decrease\)”时对应的边,那么这个边的意义即“可到达目前已出堆顶点的最小权重边”,若每次出堆时也可以得到这个边,那么出堆所得到的全部边就构成一个MST;

采用二叉堆是逻辑流程(2)有不同实现,用二叉堆的\(decrease\)可方便的把“已出堆顶点的最小权重未出堆邻接顶点”放在优先队列头部,每次\(decrease\)的代价上界为\(lgV\)。总代价\(O( (E+V)lgV ) = O( ElgV )\),若进一步利用斐波那契堆代替二叉堆,则代价为\(O(E+VlgV)\)。

但要注意Prim算法使用的二叉堆和之前文章中的二叉堆实现不太一样,需要自己建立好相关的映射关系,所以这里定义了一个叫做PrimHeap的魔改版的二叉堆来辅助算法的实现。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部