• 算法导论笔记:23最小生成树


           一个连通无向图G=(V, E),每条边(u, v) ∈ E,都有权重w(u, v),希望找到一个无环子集T E,既能够将所有节点都连接起来,又具有最小的权重。由于T是无环的,并且能连接所有的节点,所以T必然是一棵树,成T为最小生成树最小生成树不唯一。

     

    一:最小生成树的形成

           本章所讨论的两种算法都采用贪心策略,这个贪心策略由下面的通用算法描述:

           GENERIC-MST(G, w)

                  A =∅

                  while A does not form a spanningtree

                         find an edge (u, v) that issafe for A

                         A = A {(u, v)}

                  return A

     

           该通用算法遵循循环不变式:在每次循环之前,A是某颗最小生成树的一个子集。我们要做的主要事情就是选择一条边(u, v),将其加入到集合A中,使得A不违反循环不变式,也就是A∪ {(u,v)}也是某颗最小生成树的子集。称这样的边(u, v)为集合A的安全边

     

           算法的关键就在于如何找到安全边,下面介绍找安全边的规则:

           无向图G=(V, E)的一个切割(S, V-S)是集合V的一个划分,如下图所示,如果一条边(u, v)∈ E的一个端点位于集合S,另一个端点位于集合V-S,则称该条边横跨切割(S, V-S)。如果集合A中不存在横跨该切割的边,称该切割尊重集合A。在横跨一个切割的所有边中,权重最小的边称为轻量级边

           定理:设G=(V, E)是一个在边E上定义了权重函数w的连通无向图,设集合A为E的一个子集,且A包括在G的某颗最小生成树中,设(S, V-S)是图G中尊重集合A的任意一个切割,又设(u, v)是横跨切割(S, V-S)的一条轻量级边。那么边(u, v)对于集合A是安全的。

           在算法执行过程中的任何一个时刻。GA=(VA)是一个森林,GA中的每一个连通分量都是一棵树(某些树可能只包含一个顶点,例如在算法开始时,A为空集,森林中包含|V|棵树,每个顶点对应于一棵树)。

           推论:设G=(V, E)是一个在边E上定义了权重函数w的连通无向图,设集合A为E的一个子集,且A包括在G的某颗最小生成树中,设C=(Vc, Ec)为森林GA = (V, A)中的一个连通分量。如果边(u, v)是连接C和GA中某个其他分量的一条轻量级边,那么边(u, v)对于集合A是安全的。

     

    二:Kruskal算法和Prim算法

           1:Kruskal算法

           Kruskal算法找到安全边的办法是:在所有连接森林中两颗不同树的边中,找到权重最小的的边(u ,v)。设C1C2为边(u, v)所连接的两棵树。由于边(u, v)一定是连接C1和其他某棵树的一条轻量级边,根据推论,边(u, v)C1的一条安全边。

           Kruskal算法使用一个不相交集合数据结构来维护几个互不相交的元素集合,每个集合代表森林中的一棵树:

           MST-KRUSKAL(G, w)

                  A = 

                  for each vertex v

                         MAKE-SET(v)

                  sort the  edges of G.E into nondecreasing order by weight w

                  for each edge (u, v) 属于 G.E, taken in nondecreasing order by weight

                         if FIND-SET(u) != FIND-SET(v)

                                A = A {(u, v)}

                                UNION(u, v)

                  return A

     

           循环中,需要检查端点u和端点v是否属于同一棵树。如果是,该条边不能加入到森林里,否则将会形成回路,如果不是,则两个端点分别属于不同的树。

           对于图G=(V, E),Kruskal算法的运行时间依赖于不相交集合数据结构的实现方式,时间复杂度为O(ElgV)。下图为算法运行方式:

     

    2:Prim算法

           Prim算法中,集合A中的边始终构成一棵树,这棵树从任意一个根节点开始,一直长大到覆盖V中所有节点为止。算法每一步在连接集合AA之外的节点的所有边中,选择一条轻量级边加入到A中:

           MST-PRIM(G, w, r)

                  for each u ∈ G.V

                         u.key = ∞

                         u.π  = NIL

                  r.key = 0

                  Q = G.V

                  while Q != 

                         u = EXTRACT-MIN(Q)

                         for each v ∈ G.Adj[u]

                                if v ∈Q and w(u, v) < v.key

                                      v.π = u

                                       v.key = w(u, v)

     

           算法第7行将找出节点u ,该节点是某条横跨切割(V-Q, Q)的轻量级边的一个端点,然后将u从队列Q中删除,并将其加入到集合V-Q中,也就是将边(u, u.π)加入到集合A中。在for循环中,将每个与u邻接但却不在树中的节点vkey和π 属性进行更新。

           Prim算法的运行时间取决于最小优先队列Q的实现方式,如果Q采用普通的最小堆实现,则时间为O(E lg V),如果采用斐波那契堆,来实现最小队列Q,则时间将改进到O(E + V lg V),下面为算法运行过程:


  • 相关阅读:
    学点 C 语言(40): 函数 多参函数
    存取 ListBox 列表 回复 "徐强" 的问题
    博客园RSS订阅汇总
    博客园电子期刊2012年2月刊发布啦
    上周热点回顾(3.53.11)
    博客园电子期刊2012年3月刊发布啦
    上周热点回顾(3.264.1)
    上周热点回顾(3.193.25)
    上周热点回顾(4.24.8)
    上周热点回顾(2.273.4)
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247222.html
Copyright © 2020-2023  润新知