一颗带权无向图的生成树的代价是该生成树中所有边的代价之和,最小代价生成树就是一颗代价最小的生成树,构建无向图的最小生成树就是采用贪心算法,不过对于最小生成树问题,需满足以下约束条件:
- 只能使用图中的边
- 只能使用恰好n-1条边
- 不能使用产生环路的边
Kruskal算法通过每次向当前最小代价生成树T中加入一条边的方法构成最终的最小生成树T,算法按照边的代价非递减的顺序选取,并加入T中,如果所选取的边与T中的边不形成环路,则这条边加入到T中,由于图G是连通的,且具有n个顶点,所以最终恰好选取n-1条边加入到T中。
如图所示:
边 | 权值 | 结果 |
---|---|---|
… | … | 初始化 |
(0,5) | 10 | 加入树 |
(2,3) | 12 | 加入 |
(1,6) | 14 | 加入 |
(1,2) | 16 | 加入 |
(3,6) | 18 | 丢弃 |
(3,4) | 22 | 加入 |
(4,6) | 24 | 丢弃 |
(4,5) | 25 | 加入 |
(0,1) | 28 | 不再考虑 |
为了实现Kruskal算法,必须找出最小代价的边并将其从E中删除,如果把E中的边按权值进行排序,保存为一个顺序表,就可以有效的完成上述两个操作,但实际上,只要能够快速地找到下一条边最小代价的边,就没有要对E中的边进行排序,显然最小堆非常适合这个任务,因为最小堆可以在O(loge)时间内找出并删除下一条最小权值的边,而构造最小堆本身的时间复杂性为O(e).
伪代码实现:
//最小生成树算法
void create minTree()
{
T={};//最小生成树的集合
//T的边数需小于n-1,却无向图的边不能为空
while(T.edgesCount<n-1&&E.edgesCount>0)
{
minHeap(v,w,E);//从无向图E集合选择最小代价的边
delete(v,w,E);//删除该边
//如果新加入边不构成环,则加入,否则丢弃
if(!isCycle(v,w,T))
{
add(v,w,T)
}
else
{
discard(v,w)
}
}
//检查最小生成树的边数是否满足约束条件
if(T.egdescount!=n-1)
{
printf("No spanning tree\n");
}
}