1.问题分析
在一个有n个节点的无向连通图G = (V, E)中,V表示顶点集,E表示边集。只需n-1条边就可以使这个图连通,n-1条边要想保证图连通,就必须不含回路,所以我们只需要找出n-1条权值最小且无回路的边即可。
需要明确几个概念:
生成子图:选中一些边和所有顶点组成的图,称为原图的生成子图。
生成树:如果生成子图恰好是一棵树,称为生成树。
最小生成树:权值之和最小的生成树,称为最小生成树。
2.算法分析
为了在最小生成树的生成过程中,不产生环路,我们可以使用“切割法”。具体来说,在生成树的过程中,我们把已经在生成树的节点看成一个集合,把剩下的节点看作另一个集合,在这两个集合之间画一条切割线,从切割线经过的边上选出一条取值最小的作为新加入的边,可以形象地把这种方法称为“切割法”。
首先任选一个节点,如1号节点,把它放在U中,U = {1},那么剩下的节点即V - U = {2,3,4,5,6,7},V是图的所有顶点集合。如图2所示。
现在在连接两个集合(U和V-U)的边中找出权值最小的,通过画切割线可以很快找到节点1和节点2之间的边权值最小,选中这条边,把2号节点加入U = {1, 2}, V - U = {3, 4, 5, 6}。
再按照上述操作在连接两个集合(U和V-U)的边中找出权值最小的边,如图3所示。如此下去,直到U = V结束。
这个就是Prim算法,1957年由美国计算机科学家Robert C.Prim发现的。通过观察可以发现,Prim算法的贪心策略是:每次选取连接U和V-U的所有边中的最短边。
3.算法设计
算法设计的步骤如下所示:
步骤1:设计数据结构。用带权邻接矩阵C存储图G,bool数组s[],如果s[i] = true,说明顶点i已加入集合U,如图2所示。还有一个问题就是,从图上我们可以很直观地找出连接两个集合中的权值最小的边,但是在程序中如果穷举这些边就会很麻烦。在单源最短路径,我们只需要维护一个源点到其它点的最短距离数组dist[]即可,但是这里显然不行,我们需要维护的是V-U中的点到U的最短距离,需要两个数组,closest[j]表示V-U中的节点j到集合U中的最临近点,lowcost[j]表示这两个点之间边的权值。对于图2的求解过程,对应的closest[]和lowcost[]如下图所示: