问题的提出
如下图,假设这里有一系列的房屋,问如何铺设电线,可以使得连接所有房屋的电线的总成本最低?这是20世纪20年代早期研究最小生长树的最初动机。 (捷克数学家OtakarBorůvka完成的工作)。
最短路径树与最小生成树(MST)
上次,我们看到了Dijkstra算法如何用于在图中找到最短路径树。请注意,最短路径树可能不是MST,反之亦然。为什么这么说呢?
最小生成树(或MST)是总成本最低的生成树。
最短路径树(Shortest Path Tree, SPT),是一种使用最短路径算法生成的数据结构树。
一个是成本最低,一个是路径最短。
假设有一个图如下:
那么对于星号节点而言,它的最短路径树(左图),和最小生成树(右图)分别如下图所示。
MST算法的发展
- Borůvka提出的原始MST算法(1926)现在称为Borůvka算法。
- 后来,捷克数学家VojtěchJarník(1930)发明了一种现在称为Prim算法的算法。
- 之后,美国数学家Joseph Kruskal(1956)开发了现在称为Kruskal算法的算法,这就是我们今天要呈现的算法。
Kruskal’s Algorithm
这个算法听起来似乎很简单:从图表中删除所有边。 反复找到造成循环的且花费最小的边并将其添加回来。 最终的结果就是整个图的MST
用伪代码可以表示为这样:
function kruskal(graph):
//从图中移除所有边
Remove all edges from the graph.
//将所有边放入优先级队列(基于它们自身的权重)
Place all edges into a priority queue,based on their weight
//当优先级队列不为空时
While the priority queue is not empty:
//将边从优先级队列中取出
Dequeue an edge e from the priority queue.
//如果这条边添加上图中不会构成循环,则将此边添加到图中
If e's endpoints doesn’t create a cycle ,add that edge into the graph.
//否则,跳过该边
Otherwise, skip the edge.
来看下面的一幅带权图,我们试着用这种方法来找到该图的最小生成树。
- 首先我们先将图中的所有边删除,用虚线表示删除的边
此时,优先级队列的情况是这样的:
pq = {a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10, k:11, l:12, m:13, n:14, o:15, p:16, q:17, r:18}
- 根据算法,我们从优先级队列中取出队头 a:2.将它添加到图中,发现不构成环状,于是添加到图中(实线部分)
- 接着我们继续将优先级队列中的其他元素添加到图中,接下来是b:2,结果符合要求,于是加到队列中:
- 重复此次步骤。
- 当添加边到e:5时,我们发现此时构成了一个环状(红色部分),因此这条边不能加在图中,应该跳过这个边,继续添加接下来的边(如图):
- 当优先级中的队列为空时,表明我们已经将所有的边都遍历了一遍,此时生成的结果就是我们要找的MTS,如图:
- 结果
Kruskal’s 算法会按如下顺序输出所需的 MST:
{a, b, c, d, f, h, i, k, p}
而 MST’s 总花费(权重)为:
1+2+3+4+6+8+9+11+16 = 60