数据结构与算法 - 最小生成树
在之前学习了无向图和有向图,本节再介绍一种常用的图结构-加权无向图,从名字上就可以看出来,它是无向图的一种,与无向图不同的是,加权无向图的边带有权重,这个权重可以表示任意内容,比如地图中的距离、电网中成本等等。在这些问题中,通常比较关心如何成本最小化,下面就看看如何通过最小生成树来解决此类问题。
什么是生成树?图的生成树是一颗包含其所有顶点的无环连通子图;
什么是最小生成树?它是生成树的一种,其特点是所有边权值的和最小。
加权无向图数据结构
在介绍最小生成树实现算法之前,首先需要了解加权无向图如何表示,上一篇中介绍了无向图的表示方法,加权无向图与之类似,只不过多了一个权重值,因此需要定义边结构体,详细代码如下:
1 | public class EdgeWeightedGraph |
与无向图的表示主要有两点不同:
- 边表示方式不同:加权无向图单独定义了边结构体;
- 邻接表存储方式不同:使用 HashSet 顶点邻接边。
Prim 算法
介绍的第一种最小生成树计算方法叫做 Prim 算法,它的思想是这样:
- 将树的顶点分为未访问、已访问两部分,初始时已访问部分只有一个顶点 A;
- 然后访问顶点 A 的相邻顶点,从相邻顶点中找出未访问且边权重最小的顶点 B,将 B 加入到已访问部分;
- 递归的对 B 执行 第 2 步操作,直到所有顶点访问完成。
代码如下:
1 | public class LazyPrimMST |
对于一副包含 V 个顶点、E 条边的连通加权无向图来说,LazmPrim 实现能够在 $E\log{E}$ 的时间复杂度内完成最小生成树算法,但是 LazyPrim 算法还存在一定的改进空间,先看下下图 LazyPrim 的执行过程:
那么问题出在哪里呢?先简单说一下上面的执行过程:
- 初始时将第一个元素 0 添加的生成树中,并且将相邻边添加到优先级队列中;
- 然后从优先级队列中取出最小边 0-2,将 2 添加到生成树中,并且将 2 相邻的边添加到优先级队列(标绿部分);
- 重复第 2 步骤,这次取出的是边 2-6,将 6 添加到生成中,此时问题出现了,因为之前将边 0-6 添加到了优先级队列,而此时0、6都是生成树的顶点,所以0-6就失效了(因为0-2-6已经保证了0-6之间的连通性,所以就不需要额外的边添加到最小生成树中了),下次从优先级队列中取出 0-6 时就做了无用功。
通过上面过程 LazyPrim 算法的问题就很明显了,解决该问题的关键在于向优先级队列中插入边时,需要检测哪些边需要插入、哪些不需要,详细的过程这里就不赘述了,直接看代码:
1 | public class PrimMST |
总结
加权无向图是对无向图的一种扩展,它的边带有权重属性,最小生成树是包含所有节点,并且边权重之和最小的子树,它常被用来解决成本最小化的问题,介绍了 Prim 最小生成树算法,包括 Lazy 实现和优化实现,它的时间复杂度为$E\log{E}$,另外常用的最小生成树算法 Kruskal 算法。