图的最小生成树-普里姆算法

一、基本概念

生成树:一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。

最小生成树:把构造连通网的最小代价生成树称为最小生成树。(一棵生成树的代价就是树上各边的代价之和)。

二、经典算法

1、普里姆算法(主要针对顶点展开的,对于稠密图,即边数非常多的情况会更好一些)

主要思路如下图所示:

如上图所示,假设首先从零点出发,并将零点涂为黑色;

如上图所示,将蓝点与黑点之间的连线涂为紫色

在上面三个紫色的连线中最短的是权值为 1 的边,因此将这条边涂为红色并且将所对应的结点涂为黑色; 

将黑色与蓝色点之间的边改为紫色,若一个蓝点与多个黑点有相连,取权值最小的边作为子边。在这里看到,1 号顶点实际上可以连接 0 号顶点或者 2 号顶点,但是由于1、2间的权值比0、1间的权值小,所以将1、2间的路径涂为紫色,而0、1间的路径涂为灰色(不予考虑);同理将将0、3间的路径涂为紫色,而2、3间的路径涂为灰色。

在紫边中选取权值最小的边,并将此边涂为红色,相邻结点涂为黑色。

同理确定剩下蓝点与黑点之间的线哪些是紫色的,哪些是灰色的。

选取最小权值所对应的边及顶点

剩下的过程原理相同,如上图所示,得到最小生成树。

2、普里姆算法实现

/**
*    实验题目:
*        采用普里姆算法求最小生成树
*    实验目的:
*        领会普里姆算法求带权连通图中最小生成树的过程和相关算法设计
*    实验内容:
*        编写程序,实现求带权连通图最小生成树的普里姆算法。对于如下
*    图所示的带权连通图,输出从顶点0出发的一颗最小生成树。
*/

#include <stdio.h>
#include <malloc.h>

#define INF     32767               //定义∞
#define MAXV    100                 //最大顶点个数

typedef char InfoType;
/*-------------------------以下定义邻接矩阵类型---------------------------*/
typedef struct
{
    int no;                         //顶点编号
    InfoType info;                  //顶点信息
}VertexType;                        //顶点类型

typedef struct
{
    int edges[MAXV][MAXV];          //邻接矩阵数组(用一个二维数组存放顶点间关系(边或弧)的数据)
    int n;                          //顶点数
    int e;                          //边数
    VertexType vexs[MAXV];          //存放顶点信息(用一个一维数组存放图中所有顶点数据)
}MatGraph;                          //完整的图邻接矩阵类型

//邻接表表示法-将每个顶点的邻接点串成一个单链表
/*-----------以下定义邻接表类型--------------*/
typedef struct ArcNode
{
    int adjvex;                     //该边的邻接点编号
    struct ArcNode *nextarc;        //指向下一条边的指针
    int weight;                     //该边的相关信息,如权值(用整型表示)
}ArcNode;                           //边结点类型

typedef struct VNode
{
    InfoType info;                  //顶点其他信息
    int cnt;                        //存放顶点入度,仅用于拓扑排序
    ArcNode *firstarc;              //指向第一条边
}VNode;                             //邻接表结点类型

typedef struct
{
    VNode adjlist[MAXV];            //邻接表头结点数组
    int n;                          //图中顶点数
    int e;                          //图中边数
}AdjGraph;                          //完整的图邻接表类型

/*-------------------------邻接矩阵的基本运算算法---------------------------*/
/*------------由边数组A、顶点数n和边数e创建图的邻接矩阵g--------------------*/
void CreateMat(MatGraph &g, int A[MAXV][MAXV], int n, int e)
{
    int i, j;

    g.n = n;
    g.e = e;
    for(i = 0; i < g.n; i++)
        for(j = 0; j < g.n; j++)
            g.edges[i][j] = A[i][j];
}

/*------------输出邻接矩阵g--------------------*/
void DispMat(MatGraph g)
{
    int i, j;

    for(i = 0; i < g.n; i++)
    {
        for(j = 0; j < g.n; j++)
        {
            if(g.edges[i][j] != INF)
                printf("%4d", g.edges[i][j]);
            else
                printf("%4s", "∞");
        }
        printf("\n");
    }
}

/*-------------------------邻接表的基本运算算法---------------------------*/
/*-------------------由边数组A、顶点数n和边数e创建图的邻接表G--------------------*/
void CreateAdj(AdjGraph *&G, int A[MAXV][MAXV], int n, int e)
{
    int i, j;
    ArcNode *p;

    G = (AdjGraph *)malloc(sizeof(AdjGraph));
    for(i = 0; i < n; i++)                              //给邻接表中所有头结点的指针域置初值NULL
    {
        G->adjlist[i].firstarc = NULL;
    }

    for(i = 0; i < n; i++)                              //检查邻接矩阵中的每个元素
    {
        for(j = n - 1; j >= 0; j--)
        {
            if(A[i][j] != 0 && A[i][j] != INF)          //存在一条边
            {
                p = (ArcNode *)malloc(sizeof(ArcNode)); //创建一个结点p
                p->adjvex = j;                          //邻接点编号
                p->weight = A[i][j];                    //边的权重
                p->nextarc = G->adjlist[i].firstarc;    //采用头插法插入结点p
                G->adjlist[i].firstarc = p;
            }
        }
    }
    G->n = n;
    G->e = e;
}

/*-------------------输出邻接表G--------------------*/
void DispAdj(AdjGraph *G)
{
    ArcNode *p;

    for(int i = 0; i < G->n; i++)
    {
        p = G->adjlist[i].firstarc;
        printf("%d: ", i);
        while(p != NULL)
        {
            printf("%3d[%d]->", p->adjvex, p->weight);  //邻接点编号[权重]
            p = p->nextarc;
        }
        printf("∧\n");
    }
}

/*-------------------销毁图的邻接表G--------------------*/
void DestroyAdj(AdjGraph *&G)
{
    ArcNode *pre, *p;

    for(int i = 0; i < G->n; i++)
    {
        pre = G->adjlist[i].firstarc;                   //pre指向第i个单链表的首结点
        if(pre != NULL)
        {
            p = pre->nextarc;
            while(p != NULL)                            //释放第i个单链表的所有边结点
            {
                free(pre);
                pre = p;
                p = p->nextarc;
            }
            free(pre);
        }
    }
    free(G);                                            //释放头结点数组
}

/*--------------采用普里姆算法输出图g中从顶点v出发的一棵最小生成树----------------*/

/**
*   功能:
*       采用普里姆算法输出图g中从顶点v出发的一棵最小生成树(n个顶点,n-1条边)
*   备注:
*       V:全部顶点的集合
*       U:已被挑选出来的集合
*/
void Prim(MatGraph g, int v)
{
    int lowcost[MAXV];
    int min_weight;

    int closest[MAXV];
    int i, j;
    int k;

    for(i = 0; i < g.n; i++)                                  //设置lowcost[]和closest[]的初值
    {
        lowcost[i] = g.edges[v][i];
        closest[i] = v;
    }

    for(i = 1; i < g.n; i++)                                  //找出n-1个顶点
    {
        min_weight = INF;
        for(j = 0; j < g.n; j++)                              //在(V-U)中找出距离U最近的顶点k
        {
            if(lowcost[j] != 0 && lowcost[j] < min_weight)
            {
                min_weight = lowcost[j];
                k = j;
            }
        }
        printf("  边(%d,%d)权值为:%d\n", closest[k], k, min_weight);
        lowcost[k] = 0;                                      //标记k已经加入U
        for(j = 0; j < g.n; j++)                             //修改数组lowcost和closest
        {
            if(g.edges[k][j] !=0 && g.edges[k][j] < lowcost[j])
            {
                lowcost[j] = g.edges[k][j];
                closest[j] = k;
            }
        }
    }
}

int main(void)
{
    int v = 0;
    MatGraph g;
    int n = 6;
    int e = 10;
    int A[MAXV][MAXV] = {
        {0, 5, 8, 7, INF, 3},
        {5, 0, 4, INF, INF, INF},
        {8, 4, 0, 5, INF, 9},
        {7, INF, 5, 0, 5, 6},
        {INF, INF, INF, 5, 0, 1},
        {3, INF, 9, 6, 1, 0}
    };

    CreateMat(g, A, n, e);              //创建图的邻接矩阵
    printf("图G的邻接矩阵:\n");
    DispMat(g);
    printf("普里姆算法求解最小生成树:\n");
    Prim(g, v);

    return 0;
}

输出结果:

图G的邻接矩阵:
   0   5   8   7  ∞   3
   5   0   4  ∞  ∞  ∞
   8   4   0   5  ∞   9
   7  ∞   5   0   5   6
  ∞  ∞  ∞   5   0   1
   3  ∞   9   6   1   0
普里姆算法求解最小生成树:
  边(0,5)权值为:3
  边(5,4)权值为:1
  边(0,1)权值为:5
  边(1,2)权值为:4
  边(4,3)权值为:5

  • 14
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值