生成树:一个连通图的生成树是指一个极小连通子图,含有图中的全部n个顶点,但只有足以构成一棵树的n-1条边。
构造网的一棵最小生成树,即:在e条带权的边中选取n-1条边,不构成回路,使“权值之和”最小。
最小生成树要解决的问题:
- 尽可能选取权值小的边,但不能构成回路;
- 选取n-1条恰当的边以连接网的n个顶点。
Prim算法(普里姆算法)
Prim算法思想
取图中任意一个顶点v作为生成树的根,之后往生成树上添加新的顶点w,注意添加的这个顶点要使该边的权值在所有连通顶点v和w之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有n个顶点为止。
一般情况下所添加的顶点应满足条件:
在生成树的构造过程中,图中n个顶点分属两个集合:已落在生成树上的顶点集U和尚未落在生成树上的顶点集V-U;则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
看例子。首先,选择一个顶点a在生成树U中,
要保证权值最小,选e,d,c,b,然后选择g,
剩下的里面权值18,16,21,最小的,就是16了,所以选g。g和f之间27,d 和f之间21,最后选中f。
连通网用带权的邻接矩阵表示,并设置一个辅助数组closedge[],数组元素下标对应当前V-U集中的顶点序号,元素值则记录该顶点和U集中相连接的代价最小(最近)边的顶点序号adjvex和权值lowcost。即对v∈V-U的每个顶点,closedge[v]记录所有与v邻接的、从U到V-U的那组边中的最小边的信息。
简单来说,就是先任取一点a作为生成树的根,选了就把1a的lowcost置为0,然后选它的邻点,考虑2b,5e,7g,将它们的adjvex置为1a,lowcost填入权值,选取最小权值的5e,将其lowcost置0,以此类推下去,后面选的点的adjvex填入从哪个点过来的,如下图所示。
struct{
vertexData adjvex;
int lowcost;
}closedge[MAX_VERTEX_NUM];
MiniSpanTree_Prim(AdjMatrix gn, VertexData u){//邻接矩阵的网,选中作为生成树根的点
k=LocateVertex(gn,u);//确定根在网里的下标
closedge[k].lowcost=0;
for(i=0;i<gn.vexnum;i++)//将剩下的n-1个点加入集合中
if(il=k){
closedgclil.adjvex=u;
closcdgc/i].lowcost=gn.arcs/kllil.adj;//只要当前节点不是出发点,就把那一行都填为出发点
}
for(e=1;e<=gn. vexnum-1;e++){
kO=Minium(closedge);
u0=closedge[k0].adjvex;
v0=gn.vexs[k0];
printf(uO,v0);
closedge[k0].lowcost=0;
for(i=0;i<vexnum;i++)
if(gn. arcs/kOllil.adj < closedge[i].lowcost){
closedge[i].lowcost=gn.arcs[k0][i].adj;
closedgeli].adjvex=vo;
}
}
}
外层循环n-1次,内层n次,时间复杂度为O(n2),这个算法适合顶点数少的网络,即稠密图。
Kruskal算法(克鲁斯卡尔算法)
为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。
具体做法:先构造一个只含n个顶点的子图SG,然后从权值最小的边开始,若它的添加不使SG中产生回路则在SG上加上这条边,如此重复,直至加上n-1条边为止。也称为加边法。
举例说明,先将所有的边按权值从小到大排序,写出只含所有顶点的子图,
然后从权值最小的边开始,往子图里添加。
(C,D)加进去就会产生回路,说明C,D已经在边集里面了,删掉这条边,然后继续,直到所有点都加入顶点集,n-1条边加入边集,最后得到红色部分的就是最小生成树:
Kruskal算法权值排序用到堆排序算法,后面才会讲到,还有要判断是否有回路产生,所以算法具体实现这里就不赘述了。Kruskal算法更适合于顶点多而边数少的稀疏图。
图的生成树不唯一,从不同的顶点出发进行遍历,可以得到不同的生成树;
即使从相同的顶点出发,在选择最小边时,可能有多条同样的边可选,此时任选其一。
比较两种算法:
算法名 | 普里姆算法 | 克鲁斯卡尔算法 |
---|---|---|
时间复杂度 | O(n2) | O(eloge) |
适应范围 | 稠密图 | 稀疏图 |