}
i f(| T | = =n-1) T 是最小耗费生成树
e l s e 网络不是互连的,不能找到生成树
图 13-13 Kruskao 算法的伪代码
(2) 正确性证明
利用前述装载问题所用的转化技术可以证明图 1 3 - 1 3 的贪婪算法总能建立一棵最小耗费生成树。需
要证明以下两点: 1) 只要存在生成树,K r u s k a l 算法总能产生一棵生成树; 2) 产生的生成树具有最小
耗费。令 G 为任意加权无向图(即 G 是一个无向网络)。从 1 2 . 11 . 3 节可知当且仅当一个无向图连通时它
有生成树。而且在 Kruskal 算法中被拒绝(丢弃)的边是那些会产生环路的边。删除连通图环路中的一条边
所形成的图仍是连通图,因此如果 G 在开始时是连通的,则 T 与 E 中的边总能形成一个连通图。也就是若
G 开始时是连通的,算法不会终止于 E= 和| T |< n- 1。
现在来证明所建立的生成树 T 具有最小耗费。由于 G 具有有限棵生成树,所以它至少具有一棵最小生
成树。令 U 为这样的一棵最小生成树, T 与 U 都刚好有 n- 1 条边。如果 T=U, 则 T 就具有最小耗费,那么不
必再证明下去。因此假设 T≠U,令 k(k >0) 为在 T 中而不在 U 中的边的个数,当然 k 也是在 U 中而不在 T
中的边的数目。 通过把 U 变换为 T 来证明 U 与 T 具有相同的耗费,这种转化可在 k 步内完成。每一步使在 T
而不在 U 中的边的数目刚好减 1。而且 U 的耗费不会因为转化而改变。经过 k 步的转化得到的 U 将与原来的
U 具有相同的耗费,且转化后 U 中的边就是 T 中的边。由此可知, T 具有最小耗费。每步转化包括从 T 中移
一条边 e 到 U 中,并从 U 中移出一条边 f。边 e 与 f 的选取按如下方式进行:
1) 令 e 是在 T 中而不在 U 中的具有最小耗费的边。由于 k >0,这条边肯定存在。
2) 当把 e 加入 U 时,则会形成唯一的一条环路。令 f 为这条环路上不在 T 中的任意一条边。
由于 T 中不含环路,因此所形成的环路中至少有一条边不在 T 中。
从 e 与 f 的选择方法中可以看出, V=U+ {e} -{ f } 是一棵生成树,且 T 中恰有 k- 1 条边不在 V 中出现。现
在来证明 V 的耗费与 U 的相同。显然,V 的耗费等于 U 的耗费加上边 e 的耗费再减去边 f 的耗费。若 e 的耗
费比 f 的小,则生成树 V 的耗费比 U 的耗费小,这是不可能的。如果 e 的耗费高于 f,在 K r u s k a l 算法
中 f 会在 e 之前被考虑。由于 f 不在 T 中,Kruskal 算法在考虑 f 能否加入 T 时已将 f 丢弃,因此 f 和 T 中
耗费小于或等于 f 的边共同形成环路。通过选择 e,所有这些边均在 U 中,因此 U 肯定含有环路,但是实际
上这不可能,因为 U 是一棵生成树。e 的代价高于 f 的假设将会导致矛盾。剩下的唯一的可能是 e 与 f 具有
相同的耗费,由此可知 V 与 U 的耗费相同。
(3) 数据结构的选择及复杂性分析
为了按耗费非递减的顺序选择边,可以建立最小堆并根据需要从堆中一条一条地取出各边。当图中有
e 条边时,需花(e) 的时间初始化堆及 O ( l o ge) 的时间来选取每一条边。边的集合 T 与 G 中的顶点一起定义
了一个由至多 n 个连通子图构成的图。用顶点集合来描述每个子图,这些顶点集合没有公共顶点。为了确定
边( u,v)是否会产生环路,仅需检查 u,v 是否在同一个顶点集中(即处于同一子图)。如果是,则会形成
一个环路;如果不是,则不会产生环路。因此对于顶点集使用两个 F i n d 操作就足够了。当一条边包含在 T
中时,2 个子图将被合并成一个子图,即对两个集合执行 U n i o n 操作。集合的 F i n d 和 U n i o n 操作可利
用 8 . 1 0 . 2 节的树(以及加权规则和路径压缩)来高效地执行。F i n d 操作的次数最多为 2e,Un i o n 操
作的次数最多为 n- 1(若网络是连通的,则刚好是 n- 1 次)。加上树的初始化时间,算法中这部分的复杂
性只比 O (n+e) 稍大一点。
对集合 T 所执行的唯一操作是向 T 中添加一条新边。T 可用数组 t 来实现。添加操作在数组
的一端进行,因为最多可在 T 中加入 n- 1 条边,因此对 T 的操作总时间为 O (n)。
总结上述各个部分的执行时间,可得图 1 3 - 1 3 算法的渐进复杂性为 O (n+el o ge)。