62-Prim算法

  Prim算法(也称为普里姆算法)可用于在带权连通图中搜索最小生成树,是解决最小生成树问题,非常经典的算法。



问题描述:
  G = (V,E)是一个具有n个顶点的带权连通无向图

  T = (U,TE)是图G的最小生成树,其中U是T的顶点集合,TE是T的边集合

这里写图片描述
图1-无向带权连通图


1. 构造最小生成树的步骤

由图G构造最小生成树T的步骤:

这里写图片描述
图2-构造最小生成树T的步骤

  1 . 在开始的时候,如图2所示,我们根据图G中的顶点集合V中随机选取一个顶点0, 作为生成树中的顶点集合U中的初始点,顶点0到集合V - U中的所有顶点的边为候选边

  2 . 重复以下步骤n-1次,在U中加入其他n-1个顶点。
    (1)从候选边中挑选权值最小的边加入TE,设该边在集合V-U中的顶点是k,将k加入U中;

    (2)考察当前集合V-U中的所有顶点i,修改候选边:若(k,i)的权值小于原来和顶点k关联的候选边,则用(k,i)取代后者作为候选边,因为我们在选取候选边时,总是选择权值最小的边


2. Prim算法示例演示(起始点0)

这里写图片描述
图3-Prim算法示例演示1

  这个带权连通无向图中,选取顶点0作为生成树的集合U中的起始点,而顶点0作为起始点,(0,1)有一条边,(0,2)有一条边,(0,3)也有一条边,因此我们可以从这三条边中选择权值最小的边,即(0,2)这条边加入到生成树中。



这里写图片描述
图4-Prim算法示例演示2

  当把顶点2加入到集合U后,我们发现可以选择的候选边更多了。比如顶点2有(2,1),(2,3),(2,4),(2,5)四条边。通过对比这几条边的权值发现,只有(2,5)这条边的权值最小,即把(2,5)这条边加入到生成树中。



这里写图片描述
图5-Prim算法示例演示3

  当把顶点5加入到集合U后,顶点5有(5,4)和(5,3)两条候选边,再次通过对比这几条边的权值,发现只有(5,3)这条边的权值是最小的,于是就可以把(5,3)这条边加入到生成树中。



这里写图片描述
图6-Prim算法示例演示4

  当把顶点3加入到集合U后,根据构造生成树的几条准则,(3,0)和(3,2)这两条边会出现回路,所以不能加入到生成树中,那么只有(2,1)这条边的权值最小,因此可以把(2,1)这条边加入到生成树当中。



这里写图片描述
图7-Prim算法示例演示5

   当把顶点4加入到集合U中后,即(1,4)这条权值最小的边,最终就构成了一棵生成树。



  通过这个过程我们知道Prim算法在构造生成树时,每次都是选取权值最小的边,保证每一次的选择都是最优的,这其实跟贪心算法的思想是很类似的,另外,我们在构造一棵生成树的时候还需要注意几条构造生成树的准则。

3. Prim算法构造最小生成树的过程

  对于Prim算法的原理我们已经介绍完了,但是对于Prim算法如何把权值最小的边加入进来,组成最小生成树的,相信有很多同学也想知道这其中到底是怎么一回事,其实要理解这一点,我们需要来了解Prim算法中两个比较关键的数组:lowcost数组和closest数组。

int lowcost[MAXV];      //记录从U到U-V的边的最小权值
int closest[MAXV];      //记录最小权值的边对应的顶点

这里写图片描述
图8-closest和lowcost数组

  lowcost数组主要是记录集合U中的顶点到V-U集合中所有顶点的边的最小权值。而lowcost数组中的下标0 -5就代表顶点0 - 5,而lowcost[0]数组元素的值就代表边的权值。

  图G作为一个带权无向图,是采用邻接矩阵存储的,那么邻接矩阵的第n行就是顶点n到达其他顶点的边的权值。换句话说,邻接矩阵的第0行中就是顶点0到达其他顶点的边的权值。

  比如集合U中的顶点0到集合V-U中的顶点1的权值为6,那么lowcost[1]的值就为6,而顶点0无法直接到达顶点4或顶点5,因此lowcost[4]和lowcost[5]的值用符号”∞”表示(即无穷大)。

  closest数组主要是记录权值最小的边对应的顶点(可以认为是记录集合U中的顶点)。因为在开始时,集合U中只有一个顶点0。



这里写图片描述
图9-构造最小生成树1

  下面我们来看一下这个顶点2是如何加入到集合U中的,有同学会说当然是从V-U集合中搜索最小权值的边了,而lowcost数组中则恰好是存储了这样的顶点的,因此就需要从lowcost数组中搜索,找权值不为0,且小于无穷大的邻接点了,同时这个邻接点的权值还必须是最小的,于是我们就能从lowcost数组中搜索顶点0的所有邻接点,找到了权值最小的邻接点,即顶点2。

  当我们确定了要把顶点2加入到集合U时,同时还必须修改lowcost数组中顶点2的权值为0,表示顶点0到顶点2是没有距离的,已经加入到集合U中。

  当顶点2已经加入到集合U后,还要修改顶点2到其他顶点的边的最小权值(lowcost数组主要是记录集合U中的顶点到V-U集合中所有顶点的边的最小权值),比如:(2,1),(2,3),(2,4),(2,5)这些边的权值,因为(2,1)这条边的权值为5,所以lowcost[1]的值修改为5,因为(2,3)这条边的权值本来就是5,所以lowcost[3]的值不需要修改,(2,4)对应的lowcost[4]的值修改为6,(2,5)对应的lowcost[5]的值修改为4。

  同时还要修改closest数组中closest[1],closest[4],closest[5]的值修改为2,此时closest数组中记录权值最小的边对应的顶点,也就是说closest[1] = 2就代表(2,1)这条边最小权值的顶点2,其他以此类推……



这里写图片描述
图10-构造最小生成树2

  然后我们根据顶点2再从lowcost数组中搜索权值最小的边的顶点,发现lowcost[5] = 4的权值是最小的,因此把顶点5加入到集合U中。同时修改lowcost[5] = 0,表示顶点2到顶点5没有距离(权值为0),顶点5已经加入到集合U中了。

  然后再调整顶点5到其他顶点的边的权值,即(5,3)和(5,4)这两条边,因为(5,3)这条边的权值是2 ,所以在lowcost[3] = 5修改为2,因为(5,4)这条边的权值本来就是6,所以lowcost[4]的值不用修改。

  同时还要修改closest数组中closest[3]的值修改为5,此时closest数组中记录权值最小的边对应的顶点,也就是说closest[3] = 5就代表(5,3)这条边最小权值的顶点5



这里写图片描述
图11-构造最小生成树3

  然后根据顶点5从lowcost数组中搜索权值最小的边的顶点,发现lowcost[3] = 2的权值是最小的,因此把顶点3加入到集合U中,同时把lowcost[3]的值修改为0,此时3没有邻接点(因为顶点0和顶点2会出现回路,所以不能成为顶点3的邻接点),不需要再调整lowcost数组和closest数组了。



这里写图片描述
图12-构造生成树4

  从lowcost数组中搜索权值最小的边的顶点,发现lowcost[1] = 5的权值是最小的,把顶点1加入到集合U中,同时把lowcost[1]的值修改为0,再调整顶点1到其他顶点的边的权值,即(1,4)这条边,(1,4)因为的权值是3,所以将lowcost[4]的值修改为3。

  同时还要修改closest数组中closest[4]的值修改为1,此时closest数组中记录权值最小的边对应的顶点,即顶点1 。



这里写图片描述
图13-构造生成树5

  最后从lowcost数组中再次搜索权值最小的边,把顶点4加入到集合U中,同时把lowcost[4]的值修改为0。此时lowcost数组的值全部都为0,说明全部的顶点已经都加入到集合U中,形成了最终最小的生成树

4. Prim算法实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXV 6

#define INF 99

//图的定义:邻接矩阵
typedef struct MGRAPH{
    int n;                  //顶点数
    int e;                  //边数
    int edges[MAXV][MAXV];  //邻接矩阵
} MGraph;


//Prim算法
void Prim(MGraph g,int v)
{
    //变量声明
    int lowcost[MAXV];
    int min;
    int closest[MAXV],i,j,k;
    //对lowcost和closest数组进行初始化
    for (i=0; i<g.n; i++)
    {
        //把邻接矩阵中的每一行赋值到lowcost数组中
        lowcost[i]=g.edges[v][i];
        //把closest数组初始为0(因为我们是以顶点0为起始点的)
        closest[i]=v;
    }

    //除了顶点0外,需要搜索其他所有顶点
    for (i=1; i<g.n; i++)
    {
        //找权值最小的邻接点k
        //min一开始赋值为无穷大
        min=INF;
        for (j=0; j<g.n; j++)
            //找权值不为0,且小于无穷大的邻接点了
            if (lowcost[j]!=0 && lowcost[j]<min)
            {
                min=lowcost[j];    //这一步操作是为了找最小权值
                k=j;                //k会记录权值最小的邻接点
            }

    //然后输出邻接点信息
    printf(" \t边(%d,%d),权值:%d\n",closest[k],k,min);

    //调整lowcost和closest
    lowcost[k]=0;      //把找到的邻接点置为0
    for (j=0; j<g.n; j++)
        //在调整的时候,权值不能为0,且权值要小于lowcost数组中原有的权值
        if (g.edges[k][j]!=0 && g.edges[k][j]<lowcost[j])
        {
            lowcost[j]=g.edges[k][j];
            closest[j]=k;
        }
    }
}


int main(void)
{
    //用99数值代表无穷大
    int A[MAXV][MAXV] = {
        {0,6,1,5,99,99},
        {6,0,5,99,3,99},
        {1,5,0,5,6,4},
        {5,99,5,0,99,2},
        {99,3,6,99,0,6},
        {99,99,4,2,6,0}
                        };

    int i;
    int j;
    //以顶点0为起始点
    int v = 0;
    //定义邻接矩阵存储结构
    MGraph g;
    //边数
    g.e = 10;
    //顶点数
    g.n = 6;
    for(i = 0; i < g.n; i++)
    {
        for(j = 0; j < g.n; j++)
        {
            g.edges[i][j] = A[i][j];
        }
    }

    printf("\n");
    printf("最小生成树:\n");
    //Prim算法
    Prim(g,v);
    printf("\n");
    return 0;
}

测试结果:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_35733751/article/details/81208573