问题:如图假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1 条线路,如何在最节省经费的前提下建立这个通讯网?
该问题等价于:构造网的一棵最小生成树,即:在 e 条带权的边中选取 n-1条 (不构成回路),使 “权值之和” 为最小。
最小代价生成树(Minimum Cost Spanning Tree)(简称为最小生成树)。
一棵生成树的代价就是树上各边的代价之和。
构造最小生成树可以有多种算法。其中多数算法利用了最小生成树的下列一种简称为 M S T MST MST 的性质:假设 N = ( V , { E } ) N=(V,\ \{E\}) N=(V, {E}) 是一个连通网, U U U 是顶点集 V V V 的一个非空子集。若 ( u , v ) (u,\ v) (u, v) 是一条具有最小权值(代价)的边,其中 u ∈ U u\in U u∈U, v ∈ V − U v\in V-U v∈V−U ,则必存在一棵包含边 ( u , v ) (u,\ v) (u, v) 的最小生成树。
假设 N = ( V , { E } ) N=(V,\ \{E\}) N=(V, {E}) 是连通网, T E TE TE 是 N N N 上最小生成树中边的集合。
算法从 U = { u 0 } ( u 0 ∈ V ) U=\{\ u_{0}\ \}\ \ (u_{0} \in V) U={ u0 } (u0∈V) , T E = { } TE=\{\ \} TE={ } 开始,重复执行下述操作:在所有 u ∈ U u\in U u∈U , v ∈ V − U v \in V-U v∈V−U 的边 ( u , v ) ∈ E (u,\ v )\in E (u, v)∈E 中找一条代价最小的边 ( u 0 , v 0 ) (u_{0},\ v_{0}) (u0, v0) 并入集合 T E TE TE,同时 v 0 v_{0} v0 并入 U U U,直至 U = V U=V U=V 为止。此时 T E TE TE 中必有 n − 1 n-1 n−1 条边,则 T = ( V , { T E } ) T=(V,\ \{TE\}) T=(V, {TE}) 为 N N N 的最小生成树。
可取图中任意一个顶点v作为生成树的根,之后若要往生成树上添加顶点w,则在顶点v和顶点w之间必定存在一条边,并且该边的权值在所有连通顶点v和w之间的边中取值最小。
一般情况下,假设n个顶点分成两个集合:U (包含已落在生成树上的顶点)和 V-U (尚未落在生成树上的顶点),则在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
假设以二维数组表示网的邻接矩阵,且令两个顶点之间不存在的边的权值为机内允许的最大值(INT_MAX),则普里姆算法如下所示:
//此代码图的存储结构采用的是邻接矩阵
void MiniSpanTree_PRIM(MGraph G, VertexType u){
//用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。
//记录从顶点集U到V-U的代价最小的边的辅助数组定义:
//struct {
// VertexType adjvex;
// VRType lowcost;
//}closedge[MAX_VERTEX_NUM];
k = LocateVex(G, u);
for(j=0; j<G.vexnum; ++j){ //辅助数组初始化
if(j!=k){
closedge[j] = {u, G.arc[k][i].adj}; //{adjvex, lowcost}
}
}
closedge[k].lowcost = 0; //初始 U = {u}
for(i=1; i<G.vexnum; ++i){ //选择其余G.vexnum-1个顶点
k = minimum(closedge); //求出T的下一个结点;第k顶点
//此时 closedge[k].lowcost = MIN{closedge[vi].lowcost | closedge[vi].lowcost>0, vi 属于 V-U )
printf(closedge[k].adjvex, G.vexs[k]); //输出生成树的边
closedge[k].lowcost = 0; //第k顶点并入U集
for(j=0; j<G.vexnum; ++j){
if(G.arcs[k][j].adj < closedge[j].lowcost){ //新顶点并入U后重新选择最小边
closedge[j] = {G.vexs[k], G.arcs[k][j].adj};
}
}
}
} //MiniSpanTree_PRIM
假设连通网 N = ( V , { E } ) N=(V,\ \{E\}) N=(V, {E}) ,则令最小生成树的初始状态为只有 n n n 个顶点而无边的非连通图 T = ( V , { } ) T=(V,\ \{\ \}) T=(V, { }) ,图中每个顶点自成一个连通分量。在 E E E 中选择代价最小的边,若该边依附的顶点落在 T T T 中不同的连通分量上,则将此边加入到 T T T 中,否则舍去此边而选择下一条代价最小的边。依次类推,直至 T T T 中所有顶点都在同一连通分量上为止。
为使生成树上边的权值之和最小,显然,其中每一条边的权值应该尽可能地小。克鲁斯卡尔算法的做法就是:先构造一个只含 n 个顶点的子图 SG ,然后从权值最小的边开始,若它的添加不使 SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1 条边内止。
算法:
构造非连通图 S T = ( V , { } ) ; ST=(V,\ \{\ \ \}); ST=(V, { });
k=i=0;
while(k++i;
//从边集E中选取第i条权值最小的边(u,v);
//若(u,v)加入ST后不使ST中产生回路,
//则输出边(u,v),且k++;
}
假设连通网 N = ( V , { E } ) N=(V,\ \{E\}) N=(V, {E}) ,则令最小生成树的初始状态为只有 n 个顶点而无边的非连通图 T = ( V , { } ) T=(V,\ \{ \}) T=(V, {}) ,图中每个顶点自成一个连通分量。在 E E E 中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加人到 T T T 中,否则舍去此边而选择下一条代价最小的边。依次类推,直至 T T T 中所有顶点都在同一连通分量上为止。
由于普里姆算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),则适于稠密图;
克鲁斯卡尔算法需对 e 条边按权值进行排序,其时间复杂度为 O ( e l o g 2 e ) O(elog_{2}e) O(elog2e),则适于稀疏图.
问题:若从一个连通图中删去任何一个顶点及其相关联的边,它仍为一个连通图的话,则该连通图被称为重(双)连通图
。
若连通图中的某个顶点和其相关联的边被删去之后,该连通图被分割成两个或两个以上的连通分量,则称此顶点为关节点
。
没有关节点的连通图为重(双)连通图
。
关节点的特征:
假设从某个顶点 V 0 V_{0} V0 出发对连通图进行深度优先搜索遍历,则可得到一棵深度优先生成树,树上包含图的所有顶点。
若生成树的根结点,有两个或两个以上的分支,则此顶点(生成树的根)必为关节点;
对生成树上的任意一个“顶点”,若其某棵子树的根或子树中的其它“顶点”没有和其祖先相通的回边,则该“顶点”必为关节点。
——《数据结构图 (C语言版) 严蔚敏》 学习笔记