最小生成树算法(prim&kruskal)

来源:互联网 发布:化工物性数据库 编辑:程序博客网 时间:2024/05/19 10:36

无向连通图 G = (V, E)的生成树是它的极小连通子图:连接了图中所有的顶点,有保持图连通的最少的边,且不包含回路(最少的边这一条件已经隐含了不含回路的性质,同时,生成树是一种树,也是不允许有回路的)。在所有的生成树中,权值最小的树就是最小生成树(minimum spanning tree)。由最小生成树的性质可知,它的边数比顶点数小一,而且一个图的最小生成树往往是不唯一的。

求解无向连通图的最小生成树的经典方法有两种:kruskal算法和prim算法。两个算法都是贪心思想的应用:一次生成一条安全边:

GENERIC_MSTG,W

A← 空集

While A does not form a spanning tree

Do find an edge(u, v) thia is safe for A

A ← A  (u, v)

return A

说明一下安全边的概念。无向联通图 G = (V, E)的一个割(S,V-S)是对V的一个划分。如果边(uvE的一个顶点属于S,另一个属于V-S,则称(uv)通过割(S,V-S)。如果边的集合A中没有边通过某个割,则称该割不妨害A。如果某条边的权值是通过某个割的权值最小的边,则称该边为通过这个割的一条轻边。下面给出识别安全边的规则:

设图G=V,E)为无向连通图,设AE的子集,包含于G德某个最小生成树中,设割(S,V-S)是G的任意一个不妨害A的割,如果(uv)是通过割(S,V-S)的一条轻边,那么(uv)对集合A来说是安全的。

Kruskal算法:

初始状态下生成树A是空集,每个顶点都是一棵树(一个连通分支),然后每次都将图中连接两个不同的连通分支的最小权边加入到A中,然后合并两个两个连通分支,直到图中的所有顶点都属于同一个连通分支。

算法的描述挺简单,但是有两个问题需要解决:

1.如何判断两个顶点是否属于不同的连通分支?

《算法导论》中关于该部分的论述使用了不相交集合数据结构。我在实现的时候使用一个标志数组array,长度为|V|,依次对应图中的每个顶点。array[i]array[j]不同表示顶点i和顶点j属于不同的连通分支,合并操作也很简单,将array中和array[i]相同(表示位于同一连通分支)的元素值置为array[j],这样就将顶点i所在的连通分支与j所在的分支合并。

2.如何找出属于不同连通分支的最小权边?

最直接的方法是遍历图中的所有边,找出两端处于不同的分支且权最小的边。也可以使用排序算法对边按长度进行排序。

3.如何记录生成树?

  可以为每个顶点增加一个parent域,记录在生成树中它的父母。比如边(uv)添加到A中,那么vparent域就设为u

Prim算法:

初始状态A包含任意一个顶点r,从r开始,每次都向A中添加一条连接树AG=V,A)中某个孤立顶点的轻边,直至生成树A包含了图中所有的顶点。

有效实现该算法的关键是设法较容易地选择一条轻边。我们可以借助最小优先级队列。

图中的顶点可以分为两类,一类是在A中的,已经纳入最小生成树了,另一种是不在A中的,记为B,对于这些顶点,我们需要保存它们与A中的某个顶点相连的边中的最小权值。最小优先级队列保存的就是B(尚未纳入最小生成树)中的顶点以及它们与A中某个顶点相连的边中的最小权值。   每次队首出队,设新加入A的顶点为V,那么我们要修改V的邻接点中尚未在A中(在最小优先级队列)的且与A中顶点相连的边的最小权值(比较拗口)。另外,为了记录生成树,和Kruskal算法一样,我们需要增加parent域。

代码地址:点击打开链接

原创粉丝点击