**最小生成树(Minimum Spanning Tree,MST) **是在一个给定的 无向图 G(V, E) 中求一棵树,使得这棵树拥有 G 的所有顶点,且所有的边都来自 G,并且满足整棵树的 边权和最小。
最小生成树的性质:
- MST 是棵树,因此 E = V - 1,且不存在环
- MST 不唯一,但是最小边权和唯一
- MST 的根结点可以是无向图上的任意一个结点
求解最小生成树的两个算法:Prim 和 Kruskal,其关键都在于 贪心,但是贪心的策略不一样。
Prim
Prim 采用的是 点贪心 策略。
基本思想
- 选择任意节点为起始点,加入 点集合V,初始化 边集合E为空
- 选取 集合V中的点 到 剩余节点 的 最小权值边,加入E,然后把该边连接的节点加入V
- 重复以上步骤,直至 集合V包括所有的节点
- 所求的 {V,E} 就是最小生成树
可以看出 Prim 和 Dijkstra 的思想非常相同,仅仅使用了集合 V 代替了 Dijkstra 中的起点 S。
具体实现
Prim 需要两个关键概念的实现:点集合 V、顶点 u 到集合 V 的最短距离。
- 集合 V 可以直接使用一个
bool
数组vis[]
来判断顶点是否已访问 dis[]
表示从集合 V 到达顶点 vi 的最短距离,初始化时除了起点 S 的dis[S] = 0
,其于顶点都赋为INF
(一个很大的数,可以是 0x3fffffff,防止 int 溢出)
伪代码
1 | int Prim(G) { |
- 时间复杂度:邻接矩阵 ,邻接表使用堆优化
- 可以看出,稠密图(点少边多)使用 Prim
Kruskal
Kruskal 采用的是 边贪心 策略。
基本思路
- 新建图 {V,E},V有原来图的所有顶点,而 E为空集,因而每个节点自成一个连通分量
- 在原图的边中选择权值最小的边
- 若该边依附的顶点落在新建图中 不同的连通分量,则将此边加入E
- 否则舍去此边,寻找下一条权值最小的边
- 以此类推,直至新建图 所有的节点在同一连通分量
具体实现
如何判断不同连通分量,以及合并不同分量?换个角度想,就是集合的查询与合并,因此可以使用并查集实现。
伪代码
1 | int Kruskal() { |
- 时间复杂度:
- 可以看出,稀疏图(点多边少)使用 Kruskal
C++
1 | struct Edge { |