图(四):最小生成树

文章图片均来自《数据结构(C语言版)》严蔚敏

要搞清楚最小生成树的概念我们先要明白什么是生成树

  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。

最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

最小生成树最基本的算法是Prim和Kruskal算法。而它们都是基于MST性质运作的。

MST性质:N=(V,{E})是一个连通网,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值(代价)的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。

一、普里姆(Prim)算法

1.算法解读

基本思想:

1.在图G=(V, E)V表示顶点,E表示边)中,从集合V中任取(一般用第一个点)一个顶点(例如取顶点v0)放入集合 U中,这时 U={v0},集合T(E)为空。

2.v0出发寻找与U中顶点相邻(另一顶点在V中)权值最小的边的另一顶点v1,并使v1加入U。即U={v0,v1 },同时将该边加入集合T(E)中。

3. 重复2,直到U=V为止。这时T(E)中有n-1条边,T = (U, T(E))就是一棵最小生成树。

上图展示了图a用prim算法寻找最小生成树的过程。

上图closedge是辅助数组,它由adjvex和lowcost构成。adjvex记录了到vi+1最近边的起点,lowcost记录了当前点到各个点最短边的权。

看懂这两张图基本上就理解了普里姆算法。

第一张图很容易看懂,但第二张图相信大多数人一开始是看不明白的,这里我来详细解读一下:

  • closedge辅助数组有两个参数,但我们不用建立一个结构体或类,直接把它分开建立两个数组,分别是adjvex数组和lowcost数组,用处上面已经说了。
  • closedge上边有个i,这是点的下标,在图a中对应的点不是vi而是vi+1,这个算法是默认以第一个点作为起始点,即点V1,所以这里只用列出点v2到v6
  • U是最小生成树里已经连上的点,V-U是剩下没连的点,V-U是空结束。
  • k是当前找到的最小权的边的终点,注意是Vk+1而不是Vk
  • 从第二行开始每一行代表每一步骤。

到这里我们就清楚了第二张图里各个参数的意义。

但是对于算法本身相信初学者还是非常迷惑。下面我围绕容易迷惑的点继续深入探讨:

前面我们说过先把第一个点放入U中当起始点,去找相邻点的最短边,找到最短边将其加入U,然后更新我们的辅助数组,为的是继续寻找相邻点的最短边,一直到结束。注意找最短边我们是站在上一步找的点的角度去找的,把这个点叫做当前点。如果当前点到某一点(例如图a中的v2)的距离比上个点到这个点的距离短,那么就更新到v2的距离,即更新lowcost[1]的值。

所以lowcost[i]的值的意义就是到Vi+1的最短路径,这条路径的起始点的下标是adjvex[i],终点是Vk+1,其实我们每一步做的都是找这个终点,即当前lowcost数组的最小值对应的这个点,然后更新新找点到每一点的权值和起点(更小才更新,否则不动,权值和起点是同步更新的)。

2.代码实现

closedge我用lowcost和start两个数组实现。图用邻接矩阵方式存储。

public void prim(){
    int[] start=new int[vnum];
    int[] lowcost=new int[vnum];
    for(int i=0;i

二、克鲁斯卡尔(Kruskal)算法

1.算法解读

1.将图的所有连接线去掉,只剩顶点

2.从图的边集数组中找到权值最小的边,将边的两个顶点连接起来

3.继续寻找权值最小的边,将两个顶点之间连接起来,如果选择的边使得最小生成树出现了环路,则放弃该边,选择权值次小的边

4.直到所有的顶点都被连接在一起并且没有环路,最小生成树就生成了。

上图是利用克鲁斯卡尔算法构建的最下生成树的过程。

我们每次找到最小权值边将其连起来即可,但关键问题是我们如何能判断不够成回路。

为了解决这个问题我们需要辅助数组points[],它的作用是记录从某个点出发最后能够到达哪个点的位置,注意在存放数据的时候不是双向存储,存一次就可以了。我们分别用该边的起点和终点测试,看最后结果是否相同,如果相同则构成环,舍弃该边;如果不相同则将该边存入points[]。

如边(1,3),存入points[]让points[1]=3。

详细过程可参考最小生成树(克鲁斯卡尔算法)。

2.代码实现

此次算法需要换种图的存储方式,前面写的不能沿用

public class MGraph{
    class Edge{
     int begin;
     int end;
     int wight;   
        
    }
    
    char vexs[];
    Edge edges[];
    int vnum,enum;
    
    /**
    其他函数
    **/
    public void Kruskal(){
        int m,n;
        int points[]=new int[vnum];
        for(int i=0;i

你可能感兴趣的:(图(四):最小生成树)