算法导论之图的最小生成树

来源:互联网 发布:webos电视软件下载 编辑:程序博客网 时间:2024/06/04 19:42

引出最小生成树,是提到电子线路设计时,要把数个元件的引脚连接在一起,使其电位相同。使n个引脚互相连通,可以使用n-1条连接线,每条连接线连接两个引脚。寻求连接线最少的方案,是最小生成树的应用。将电子线路引脚接线连接问题模型化求解一个无向带权连通图的顶点互联最小代价。

一个无向带权连通图G=(V,E),其中V是引脚集合(顶点),E是每个引脚之间可能互联的集合(边)。图中每一条边(u,v)∈E,都有一个权值w=(u,v)表示连接u和v的代价(引脚间接线数目)。

从这样一个图中找出一个无回路的子集T⊆E,这个子集T连接了所有顶点,且其边权值之和最小。


T无回路且连接所有顶点,是一个树,成为生成树,权值最小,即是最小生成树。求解图G的T子集的问题就是最小生成树问题。如何从一个图中生成一颗最小生成树,主要有Kruskal和Prim两个算法,时间性能都是O(ElgV)。其中Prim算法通过采用斐波那契堆可将运行时间减少到O(E+VlogV),适用于|V|远小于|E|的图求解最小生成树。

不得不说的,两个算法的思想核心是贪心算法。在算法的每一步中,都必须在几种可能性中选择一种,动态规划是都选择并求解最优子解最后组合成最优解;而贪心算法则是选择可能性中最佳的一种来求解最优子解。一般来说,贪心算法这种策略不能保证找到全局最优解,但在最小生成树问题上,通过贪心策略可以获得最小权值的生成树是可证的。

通用最小生成树算法:

假设已知一个无向连通图G=(V,E),其权值函数w:E->R,目的是找出图G的最小生成树。

通用最小生成树算法采用贪心策略,在每一个步骤都形成最小生成树的一条边。算法维护一个边集合A,保持循环不变式:

在每一次循环迭代之前,A是某个最小生成树的一个子集。

在算法的每一步中,确定一条边(u,v),是的将它加入集合A后,仍然不违反这个循环不变式,即AU{(u,v)}仍然是某一个最小生成树的子集,这样的边(u,v)为A的安全边。安全边加入子集A后,A仍然保持是某一个最小生成树的子集。

这是贪心算法思想,每一步寻找可能解中最优。那问题就是,怎么寻找安全边,确保加入A后使A仍然是最小生成树的子集。

算法导论中给出一个识别安全边的规则,适用于无向图。先给出这个规则的定理。

设图G=(V,E)是一个无向连通图,并且在E上定义了一个具有实数值的加权函数w。设A是E的一个子集,它包含于G的某个最小生成树中。设割(S,V-S)是G的任意一个不妨害A的割,且边(u,v)是通过割(S,V-S)的一条轻边,则边(u,v)对集合A来说是安全的。

这个定理中关键有三点,第一:将图分为两个子图S和V-S,即为割(S,V-S);第二:割(S,V-S)的边(u,v)是轻边,就是连接子图S和V-S所有边中最小权值的一条;第三:已经在子集A的边不能是连接割的边,称之为不妨害A的割;

算法导论中证明识别安全边的规则就不具体展开,主要是构造一个通用图,不失为普通的情况来证明。通俗地理解就是:将图分割成两个子图,两个子图间的联系的边中最小权值的就是安全边,前提是这些边不能是集合A中的。这里按归纳法证明下:

假设图G=(V,E)是一个无向连通图,

割(1,V-1),那1个顶点和V-1个顶点两个子图之间最小权值的边就是安全边了,A集合中有1个顶点,无边;

割(2,V-2),引入第二个顶点时,2和V-2割之间的边显然不会妨害A集合,找出两个割之间的最小权值也容易,加入安全边,集合A有一条边,就是(1,2);

如此类推,n个顶点加入时,割(n,V-n)中n个顶点之间的构成子图就是集合A的轻边,就是最小生成树。通用算法之上改良的有Kruskal和Prim算法。

Kruskal算法将集合A(最小生成树的顶点集和边集)是一个森林,加入集合A中的安全边总是途中连接两个不同连通分支的最小权边。而Prim算法则将集合A形成单颗树,加入集合A的安全边总是连接树与一个不在树中的顶点的最小权边。

实际,二者思想是一致,只不过通过不同的数据结构来实现,时间性能分析中还涉及到增长极为缓慢的函数。Prim算法应用二叉最小堆和斐波那契堆保存最小优先队列Q也有不同的性能效果。

Kruskal算法描述如下:

1)图G=(V,E)中的每个顶点都是一棵树,这样初始有V颗树,按照E的权值大小排序边集。初始化最小生成树,即集合A为空集;

2)按顺序找出权限的边集中的安全边,如果该边的两个顶点属于不同树,那么可以合并两棵树,并把边加入到集合A中;

3)直到所有的顶点都已在集合A中,其边权值最小。

核心思想就是贪心算法的,找出森林中权值的最小的边,然后合并该边联系的两棵树。或者从因到果的过程来看,要找出最小生成树,我就把所有边按顺序排序,依次将最小权值的边纳入集合A中。

Prim算法描述如下:

1)集合A是一颗正在成长的单棵树,树从任意顶点开始,逐渐生成直到覆盖所有顶点;

2)生成过程是将连接树A和G-A中最小权值的边加入A;

通俗地说,就是从图中的任意一个顶点出发,找到该顶点所有的边,把最小权值的边放入集合A中即可。实现该算法,要借助一个变量:最下优先级队列Q。Q保存所有顶点中与顶点相连的边中的最小权值。Q二叉最小堆实现,时间性能和Krusal算法一样,但若使用斐波那契堆可以改善。

两个算法基本都是将最小权值的边找出来,然后将安全边的顶点加入集合A中,从而逐步构成一棵正在成长的最小生成树。

这里给出平摊分析中提到的增长极快函数及其增长极慢的逆函数,有助于理解在时间性能分析中引入增长极慢函数的意义。

增长极快的函数:


10的80次方已经是可观察到的宇宙中估计的原子数量。

有点像2的64次方的故事,数学的魔力在于此,简单这样的一个函数设计,只要k=4就已经大到无边,大到目今我们所认知宇宙的数量极限。

对应增长极慢的函数,就是该函数的逆函数。对整数j≥0,定义函数Ak(j)的逆函数为:

a(j)=min{k: Ak(1)≥j}

a(j)是的函数Ak(1)至少为j的最低级别k。根据增长极快函数Ak(1)的值,可知:

a(j)=0,当0≤j≤2

a(j)=1,当j=3

a(j)=2,当4≤j≤7

a(j)=3,当8≤j≤2047

a(j)=4,当2048≤j≤A4(1)

可见如此,在实际应用中的数字一般都是只有a(j)≤4,是一个增长极为缓慢的函数,变量超级无敌大,我依旧小于4。让人不禁想起阿基米德的豪言壮语:给我一个支点,我就能支起地球。宇宙之极大又或者是极小,未可知啊。
0 0