图论算法之最小生成树(Krim、Kruskal)

图论算法之最小生成树(KrimKruskal

1、图论基础

图论 (Graph theory) 是数学的一个分支,图是图论的主要研究对象。 图 (Graph) 是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系。顶点用于代表事物,连接两顶点的边则用于表示两个事物间具有这种关系。

  • 图的相关名词解释:

    • 图的表示法:G=(V(G),E(G)),其中V(G)表示点集,E(G)表示边集;

    • 度:与一个顶点v关联的边的条数称作该顶点的度,如果是有向图的话,分为入度和出度。

  • 图的分类:有向图和无向图;是否为加权图;有环图和无环图。

  • 图的表示法:邻接矩阵和邻接表。(通常稀疏的图用邻接表,稠密的图用邻接矩阵,稀疏的图用邻接矩阵会非常浪费空间的)

  • 图的遍历:深度优先搜索和广度优先搜索。(DFS使用栈,将顶点存储在栈中,顶点是沿着路径被探索的,存在新的相邻节点就去探索;BFS使用队列,将顶点存入队列中,最先如队列的顶点先被探索)

  • 图遍历作用:可以用来寻找特定的顶点或寻找两个顶点之间的路径,检查图是否连通,检查图是否含有环等。

  • 图遍历的思想:追踪每个第一次访问的节点,并且追踪有哪些节点还没有被完全探索。对于两种遍历方法,都需要指明第一个被访问的顶点。完全探索一个顶点要求我们查看该顶点的每一条边。为了保证算法的效率,务必访问每个顶点至多两次。

2、最小生成树

最小生成树(Minimum Spanning TreeMST:无向连通图中边权和最小的生成树(最短路径连接所有节点)。

注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。

最小生成树有很多实际问题的应用,例如:要在n个城市之间铺设光缆,主要目标是要使这n个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

有两个经典的算法可以用来解决最小生成树问题:Kruskal算法和Prim算法。其中Kruskal算法中便应用了并查集这种数据结构。Prime算法维护的是顶点的集合,而Kruskal维护的是边的集合。
在这里插入图片描述

3、Kruskal算法

此算法可以视为“加边法”,即把所有cost从小到大排序,然后使用并查集依次尝试合并每个边:

  • 如果合并成功,则加入这条边;
  • 如果合并失败(边的两个节点已经合并过),说明产生了环,则丢弃这条边。

Kruskal算法 = 贪心 + 并查集

通过并查集合并后,每个连通分量节点都会有相同的root,因此检查所有节点的root

  • 如果检查到只有一个root,说明这个图只有一个连通分量,是连通图,返回cost
  • 如果检查到超过一个root,说明这个图有多个连通分量,不是一个连通图,返回-1

在这里插入图片描述

4、Krim算法

此算法可以视为“加点法”,即Kruskal算法每次添加一个最小的边,而Prim算法则是每次添加一个距离已选取节点集最近的点。

流程如下:

  • 集合S表示已选取的节点集;
  • 选任意一个节点作为起始节点 A,放到集合S中,并更新其他节点到集合S的最近距离。因为当前S中只有一个节点 A,因此更新为到节点 A 的距离;
  • 选取距离S最近的一个节点 B,放到集合S中,并更新其他节点到集合S的最近距离。也就是节点 i 的距离更新为 min { adj[A][i], adj[B][i] }
  • 继续选取、更新,直到N个节点都被选取。
    在这里插入图片描述

实际提交发现,Prim算法效果远不如Kruskal好,原因如下:

  • 题目给的是边(connections),而使用Prim算法,需要快速得到两个节点之间的距离。如果每次都直接遍历connections,复杂度太高,因此需要先转换成邻接矩阵或邻接表。选择合适的邻接矩阵或邻接表,是解决本题的一个关键。
  • 另外一个关键点就是,获取距离最小的节点,可以直接遍历,也可以借助 PriorityQueue 实现。