对于一个带权连通无向图G=(V, E),生成树不同,每颗树的权(树中所有边上权值之和)也可能不同。若T为G所有生成树中权值最小的那颗生成树,则T称为G的最小生成树。下面的2种算法就是基于贪心算法的策略提出的,算法很早提出,但经典。即使早就由代码实现过,但仍然有人在此基础上改进提出其他好的方法。
1. Prim算法
a. 基本思想:图N={V, E}是连通图,其中V是点集,V是边集。最小生成树Nt={Vt, Et}。算法从Vt={u0},Et={}开始。重复执行下述过程:从所有u∈Vt, v∈V-Vt选出一条代价最小的边(u0, v0)∈E并入集合Et,同时将v0也加入到Vt中,直至Vt=V为止。(代码实现就是使用到里面几个辅助集合,一般使用数组,链表也行)
b. 实现过程:
伪代码
void Prim(G,T){
T=ф; //初始化空树
Vt = {u}; //V中随意一个点入树中的顶点集
while(Vt!=V){ //设树中不含全部顶点
设(u,v)是使u∈Vt,v∈V-Vt,且权值最小的边;
Et = Et U (u,v); //边入树
Vt = Vt U {v}; //顶点入树
}
}
示意图
c. 性能分析:时间复杂度为O(|V|^2), 因为是从Vt的点一个个映射到V-Vt中的结点形成的边,再根据E判断最小权边,所以每个点的复杂度是O(|V|),和起来就是O(|V|^2)。适合边稠密的图
2. Kruskal算法
a. 基本思想:与Prim算法不同,Prim算法是由点找边,扩展成最小生成树。而Kruskal算法是一种按权值递增次序,选择出边来构造最小生成树。图N=(V, E)是连通网,对应的最小生成树T=(Vt, Et)。开始时,Vt=V, Et=ф。从E中按权值从小到大依次从E-Et中选择一条边,如果加入这条边后不构成回路,则将其加入Et, 否则舍弃,直到边数为n-1。
b. 实现过程:
伪代码
void Kruskal(V,T){
T=V; //初始化树T,仅含顶点
numS=n; //判断是否满足连通
while(numS>1){ //
从E-Et中最小的权值的边(v,u);
if((u,v)在Vt中不构成回路){
Et = Et U (v,u); //加入该边
numS--;
}
}
}
c. 性能分析:在这个算法中,一般选择堆存放边的集合,则每次选择最小权值边只需O(log|E|),每次求新边d都是等价类的过程,故时间复杂度O(|E|log|E|)。适合点多边少的图。