最小生成树
在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树(Minimum Cost Spanning Tree),简称为最小生成树。最小生成树有如下重要性质:
设 N=(V,{E}) 是一连通网,U 是顶点集V的一个非空子集。若(u , v)是一条具有最小权值的边,其中u∈U,v∈V-U,则存在一棵包含边(u , v)的最小生成树。
我们用反证法来证明这个性质:
假设不存在这样一棵包含边(u , v)的最小生成树。任取一棵最小生成树T,将(u , v)加入T中。根据树的性质,此时T中必形成一个包含(u , v)的回路,且回路中必有一条边(u’ , v’)的权值大于或等于(u , v)的权值。删除(u , v),则得到一棵代价小于等于T的生成树T’,且T’为一棵包含边(u , v)的最小生成树。这与假设矛盾。
这个性质被称为MST性质。我们可以利用MST性质来生成一个连通网的最小生成树。普利姆(Prim)算法和克鲁斯卡尔(Kruskal)算法便是利用了这个性质。
下面分别介绍这两种算法。
1. 普利姆算法
假设N=(V,{E})是连通网,TE为最小生成树中边的集合
(1)初始U={u0}(u0∈V),TE=φ;
(2)在所有u∈U, v∈V-U的边中选一条代价最小的边(u0,v0)并入集合TE,同时将v0并入U;
(3)重复(2),直到U=V为止。
此时,TE中必含有n-1条边,则T=(V,{TE})为N的最小生成树。
可以看出,普利姆算法逐步增加U的中顶点,可称为“加点法”。
注意:选择最小边时,可能有多条同样权值的边可选,此时任选其一。
为了实现这个算法需要设置一个辅助数组closedge[ ],以纪录从U到V-U具有最小代价的边。对每个顶点v∈V-U,在辅助数组中存在一个分量closedge[v],它包括两个域vex和lowcost,其中lowcost存储该边上的权,显然有
closedge[v].lowcoast=Min({cost(u,v) | u∈U})
普里姆算法可描述如下:
struct {
VertexData adjvex;
int lowcost;
} closedge[MAX_VERTEX_NUM]; /* 求最小生成树时的辅助数组*/
MiniSpanTree_Prim(AdjMatrix gn, VertexData u)
/*从顶点u出发,按普里姆算法构造连通网gn 的最小生成树,并输出生成树的每条边*/
{
k=LocateVertex(gn, u);
closedge[k].lowcost=0; /*初始化,U={u} */
for (i=0;i<gn.vexnum;i++)
if ( i!=k) /*对V-U中的顶点i,初始化closedge[i]*/
{closedge[i].adjvex=u; closedge[i].lowcost=gn.arcs[k][i].adj;}
for (e=1;e<=gn.vexnum-1;e++) /*找n-1条边(n= gn.vexnum) */
{
k0=Minium(closedge); /* closedge[k0]中存有当前最小边(u0,v0)的信息*/
u0= closedge[k0].adjvex; /* u0∈U*/
v0= gn.vexs[k0] /* v0∈V-U*/
printf(u0, v0); /*输出生成树的当前最小边(u0,v0)*/
closedge[k0].lowcost=0; /*将顶点v0纳入U集合*/
for ( i=0 ;i<vexnum;i++) /*在顶点v0并入U之后,更新closedge[i]*/
if ( gn.arcs[k0][i].adj <closedge[i].lowcost)
{ closedge[i].lowcost= gn.arcs[k0][i].adj;
closedge[i].adjvex=v0;
}
}
}
算法 7.9 普里姆算法
由于算法中有两个for循环嵌套,故它的时间复杂度为O(n2)。
利用该算法,对图7.18(a)所示的连通网从顶点V1开始构造最小生成树,算法中各参量的变化如表7-1所示
图 7.18 普里姆算法构造最小生成树的过程
表7-1 普里姆算法各参量的变化
i
Closedge[i] |
0 |
1 |
2 |
3 |
4 |
5 |
U |
V-U |
e
|
k0 |
(u0, v0) |
adjvex lowcost |
0 | V1 6 | V1 1 | V1 5 | V1 ∞ | V1 ∞ | {V1} | {V2,V3,V4,V5,V6} |
1 |
2 |
(V1, V3) |
adjvex lowcost |
0 | V3 5 |
0 | V1 5 | V3 6 | V3 4 | {V1,V3} | {V2,V4,V5, V6} |
2 |
5 |
(V3, V6) |
adjvex lowcost |
0 | V3 5 |
0 | V6 2 | V3 6 |
0 | {V1,V3,V6} | {V2,V4,V5} |
3 |
3 |
(V6, V4) |
adjvex lowcost |
0 | V3 5 |
0 |
0 | V3 6 |
0 | {V1,V3,V6, V4} | {V2,V5} |
4 |
1 |
(V3, V2) |
adjvex lowcost |
0 |
0 |
0 |
0 | V2 3 |
0 | {V1,V3,V6, V4,V2} | {V5} |
5 |
4 |
(V2, V5) |
adjvex lowcost |
0 |
0 |
0 |
0 |
0 |
0 | {V1,V3,V6,V4,V2,V5} | { } |
|
|
|
2. 克鲁斯卡尔算法
假设N=(V,{E})是连通网,将N中的边按权值从小到大的顺序排列;
① 将n个顶点看成n个集合;
② 按权值小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集合内,将该边放到生成树边的集合中。同时将该边的两个顶点所在的顶点集合合并;
③ 重复②直到所有的顶点都在同一个顶点集合内。
可以看出,克鲁斯卡尔算法逐步增加生成树的边,与普利姆算法相比,可称为“加边法”。
例如,对于图7.17所示的连通网,将所有的边按权值从小到大的顺序排列为:
权值 1 2 3 4 5 5 5 6 6 6
边 (V1,V3) (V4,V6) (V2,V5) (V3,V6) (V1,V4) (V2,V3) (V3,V4) (V1,V2) (V3,V5) (V5,V6)
经过筛选所得到边的顺序为:
(V1,V3),(V4,V6),(V2,V5),(V3,V6),(V2,V3)
在选择第五条边时,因为V1、V4已经在同一集合内,如果选(V1,V4),则会形成回路,所以选(V2,V3)。
下面我们以图7.19(a)中的连通网为例,详细说明克鲁斯卡尔算法的执行过程。
图 7.19 克鲁斯卡尔算法执行示意图
(1)待选的边:
(2,3)->5 , (2,4)->6 , (3,4)->6 , (2,6)->11 , (4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19,
(1,6)->21 , (5,6)->23。
顶点集合状态:{1},{2},{3},{4},{5},{6}
最小生成树的边的集合: { }。
(2从待选边中选一条权值最小的边为: (2,3)->5。
待选的边变为:(2,4)->6 , (3,4)->6 , (2,6)->11 , (4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->23。
顶点集合状态变为: {1},{2,3},{4},{5},{6}。
最小生成树的边的集合:{(2,3)}。
(3)从待选边中选一条权值最小的边为: (2,4)->6;
待选的边变为:(3,4)->6 , (2,6)->11 , (4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->23。
顶点集合状态变为:{1},{2,3,4},{5},{6}。
最小生成树的边的集合 {(2,3),(2,4)}。
(4)从待选边中选一条权值最小的边为:(3,4)->6,由于3、4在同一个顶点集合{2,3,4}内,故放弃。重新从待选边中选一条权值最小的边为:(2,6)->11。
待选的边变为:(4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->23。
顶点集合状态变为: {1},{2,3,4,6},{5}。
最小生成树的边的集合 {(2,3),(2,4),(2,6)}。
(5)从待选边中选一条权值最小的边为:(4,6)->14,由于4、6在同一个顶点集合{2,3,4,6}内,故放弃。重新从待选边中选一条权值最小的边为:(1,2)->16。
待选的边变为:(4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->23。
顶点集合状态变为: {1,2,3,4,6},{5}。
最小生成树的边的集合: {(2,3),(2,4),(2,6),(1,2)}。
(6)从待选边中选一条权值最小的边为: (4,5)->18。
待选的边变为: (1,5)->19, (1,6)->21 , (5,6)->23。
顶点集合状态变为: {1,2,3,4,6,5}。
最小生成树的边的集合 {(2,3),(2,4),(2,6),(1,2),(4,5)}。
至此,所有的顶点都在同一个顶点集合{1,2,3,4,6,5}里,算法结束。所得最小生成树如图7.20所示,其代价为:5+6+11+16+18=56。