一、应用场景
n个城市之间想要建立一个通信线路,有很多种方法,把城市看作是顶点,城市之间的线路看作是路径,则n个城市之间构建的通信线路 可以看成是一个 连通网,为了节省费用,势必是想要找到其中一个最小的 连通网的,这种找 最小 “连通网(带权路径和)”的问题,我们可以用 最小生成树算法 来解决。
最小生成树:各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树,简称最小生成树;
二、最小生成树算法
1、普里姆算法:给定一个起始顶点,求最小生成树(加点法)
普里姆算法基于一个这样的思想,即:如果<u,v>是一条具有最小权值的边,则这条边一定包含在最小生成树中;
算法思想:假设U中现有u0一个顶点,V-U中为V中其它顶点,TE为所求的最小边集合:
step1:在所有u belong to U,v belong to V-U中,找到一条权值最小的边<u,v>,并将该边的顶点v也并入集合U中,边<u,v>并入集合TE中;
step2:重复步骤1,直到U=V为止;
以一个顶点为开始,求最小生成树,如下图所示,为利用普里姆算法求 最小生成树 的图示:
普里姆算法伪代码:
//以 邻接矩阵 表示图G
struct{ //存储最短边
VerTexType adjvex;
ArcType lowcost;
}closedge[MVNum];
void MiniSpanTree_Prim(AMGraph G,VerTexType u){
k = LocateVex(G,u); //返回顶点u在G中的下标
for(i=0;i<G.vexnum;++i){ //初始化各个顶点的 最短边
if(i != k){closedge[i] = {u,G.arcs[k][i]};}
}
closedge[k].lowcost = 0;
for(i=1;i<G.vexnum;++i){
m = MinArc(closedge); //找出最短边 对应的顶点(V集合中的顶点) 下标;
u~0~ = closedge[m].adjvex;
v~0~ = G.vex[m];
cout<<u~0~<<v~0~; //将最短边输出
closedge[m].lowcost = 0;
for(i=0;i<G.vexnum;++i){
if(G.arcs[m][i] < closedge[i].lowcost){
closedge[i].lowcost = G.arcs[m][i]; //更新各个顶点的最短边
closedge[i].adjvex = G.vex[m]; //更新其邻接点信息
}
}
}
总结:
1、普里姆算法包含一个2层for循环,因此,其时间复杂度为O(n2);
2、普里姆算法运算过程中,只涉及到找顶点的操作,而没有涉及边,因此,普里姆算法的时间复杂度与边数无关,适用于 求 稠密网的最小生成树;
2、克鲁斯卡尔算法(加边法)
通过搜集 最小边的 方式 构造 最小生成树;
克鲁斯卡尔算法 具体步骤如下:
step1:假设连通网N=(V,E),将N中的边按权值从小到大的顺序排列;
step2:选取最小边,如果最小边的两个顶点不属于同一个连通分量,则将这条边纳入TE集合中,否则舍去,寻找下一条最小边。在最开始时,连通网中各个顶点自成一个连通分量;
step3:重复step2,直到所有的顶点都出现在最小生成树中;
克鲁斯卡尔算法 构成最小生成树 的过程(原连通图 同 普里姆算法中的连通图):
克鲁斯卡尔算法伪代码:
struct{
VerTexType head; //边的头结点
VerTexType tail; //边的尾结点
ArcType lowcost; //边的权重
}Edge[arcnum];
void MiniSpanTree_Kruskal(AMGraph G){
//初始化所有顶点所处连通分量编号 为自己
for(i=0;i<G.vexnum;++i){
Vexset[i] = i;
}
sort(Edge); //将边按从小到大的顺序从新排序
for(i=0;i<G.arcnum;++i){
u = LocateVex(G,Edge[i].head); //找出头结点在图G的下标
v = LocateVex(G,Edge[i].tail); //找出尾结点下标
us = Vexset[u]; //head所属连通分量
vs = Vexset[v]; //tail所属连通分量
if(us != vs){
cout<<G.vex[us]<<G.vex[vs]; //输出最短边
for(i=0;i<G.vexnum;++i){
if(Vexset[i] == vs){Vexset[i] = us;} //将一个连通分量的编号统一
}
}
}
}
总结:
1、克鲁斯卡尔算法中 sort(Edge) ,如果用堆排序的话,其时间复杂度为O(elog2e)。而for循环中最费劲的部分是合并两个不同的连通分量,只要采取合适的数据结构,可以证明其执行时间为O(log2e),因此,整个算法下来,其时间复杂度为O(elog2e)。普里姆算法的时间复杂度为:O(n2);
2、克鲁斯卡尔算法 是对边进行操作,因此,可以用于 稀疏网的 最小生成树 的生成;