文章目录
最小生成树和最短路径都是图论中比较基本的内容,我最开始在大学接触时感觉懵懵懂懂的,后来工作之后重新看算法相关的东西算是都会编代码了,但知道最近我重新把相关的内容深入学习了一遍才感觉把原理性的东西也弄明白了。当然,我所说的弄明白了只是最基本的内容。
以下只说我理解之后的干货,至于每个算法的详细及通俗介绍,网上一搜一大片,我没必要在这里重复讲了。
一、两类基本问题
1.基本问题描述
最小生成树是在连通图中找出最少的边将所有点联通,使得边的权值之和最小。所以对于有n个点的连通图,其最小生成树一定是有n-1条边。但是由于原图中边的权值可能相同,找到的最小生成树可能不是唯一的。
最短路径是给定图中两点,求最短路径。如果图是不连通的,则最短路可能不存在或者说无穷大。通常是给定起点和终点求最短路径,但一般算法都会顺带求出其他最短路径,甚至floyd算法会一次性求出所有点两两之间的最短路径。
2.基本使用条件
最小生成树要求图是连通图。连通图指图中任意两个顶点都有路径相通,通常指无向图。理论上如果图是有向、多重边的,也能求最小生成树,只是不太常见。
最短路径同样要求图是连通图,否则有些点之间就不存在路径了。通常是用于无向单重边的图。不同的算法对图中是否有负边或负权是有适用条件的,后面会再说。
二、最小生成树常用算法
1.Prim算法
基本思路
是从任取一个点作为初始集合开始,找出与集合中的点距离最小的外部点加入作为新的集合,然后不断重复直到所有点加入集合,不同点加入集合时对应的最短边的集合就构成了最小生成树。
性能分析
用V表示顶点数,E表示边数。算法的外循环最多为V-1次,如果图用邻接矩阵存储则内循环也需要O(V)次,因而基本算法复杂度为O(V^2)。
但是如果用堆结构来维护已访问过的点的集合到未访问过的点的距离时,时间复杂度可以优化到O(ElogV),这时内循环就没有了,而变成了O(E)次存取边的操作,而每次存取的时间复杂度为logV(因为用的是堆结构)。后面讲到Dijkstra最短路径算法也有类似的优化。
适用场景
通常图为稠密图时用Prim算法相比Kruskal会比较好。
而上述优化的适用条件是图为稀疏图,这时E远小于V^2。如果是稠密图,则此优化无意义。
所以稀疏图情况下,使用堆的Prim算法与Kruskal算法复杂度相当。但是由于写法上Kruskal算法更简洁方便,所以Kruskal算法更常用。
延伸讨论
用斐波那契堆能够进一步降低复杂度到O(V * lgV)。参考博文Dijkstra算法与Prim算法的异同
2.Kruskal算法
基本思路
是将所有的边排序,然后从最小的边开始,只要满足加入边不构成环,就加入集合,直到加入n-1条边,就构成了最小生成树。
性能分析
算法分为排序和主体两步。对所有边排序的复杂度为O(ElogV),至于后面最小生成树的主体部分,则是O(E)的,因为Kruskal算法通常会配并查集数据结构来用于判环,该数据结构在稳定状态下复杂度为常数级。所以Kru