最小生成树之普里姆算法

一、最小生成树

1、如下图所示,假如V0~V8表示9个不同的村庄,现在要在这9个村庄中搭建通信网络,其中的权值表示村庄与村庄之间的可通达的直线距离,没有连线的表示不予考虑,距离越大成本就越高,要求用最小的成本完成该任务。那么该在这9个村庄中怎么布局呢。


  这是一个带权值的图,即网图,所谓的最小成本就是n个顶点,用n-1条边把一个连通图连接起来,且使得权值的和最小,所以在该图中只要连接的距离最少,成本就最少了。如下有几种方案:

  • 方案一:如下图所示,所需成本为11+26+20+24+19+21+22+18=161

  • 方案二:如下图所示,所需成本为10+12+8+11+17+19+16+7=100

  • 方案三:如下图所示,所需成本为8+12+16+10+11+19+16+7=99

  其实这就是求图的最小生成树的问题,上面的三个方案都是该网图的生成树,但是只有方案三才是最小生成树,关键是如何求的图的最小生成树呢?
二、普里姆算法
1、如下图所示,用普里姆算法求该图的最小生成树的过程为:


  • 第一步:选择一个出发点,比如V0,将其背景色由白色变成黑色:

  • 第二步:把结点V0与相邻结点间的边变成紫色:

  • 第三步:在紫色的边中选取一条权值最小的边,将此边变成红色,相邻结点变色黑色:




  • 第四步:将黑色顶点与白色顶点间的边变成紫色,若一个白色顶点与多个黑色顶点有边相连,则选取权值最小的边变成紫色,其它的边变成灰色,表示不考虑

  • 第五步:在紫色边中选择一条权值最小的边,将此边变成红色,相邻结点变成黑色:

  • 第六步:将黑色顶点与白色顶点间的边变成紫色,若一个白色顶点与多个黑色顶点有边相连,则选取权值最小的边变成紫色,其它的边变成灰色,表示不考虑

  • 第七步:在紫色边中选择一条权值最小的边,将此边变成红色,相邻顶点变成黑色:

  • 第八步:在紫色边中选择一条权值最小的边,将此边变成红色,相邻顶点变成黑色:

  • 第九步:将黑色顶点与白色顶点间的边变成紫色,若一个白色顶点与多个黑色顶点有边相连,则选取权值最小的边变成紫色,其它的边变成灰色,表示不考虑:

  • 第十步:在紫色边中选择一条权值最小的边,将此边变成红色,相邻顶点变成黑色。普里姆算法构造最小生成树结束,黑色顶点和红色边组成的树就是最小生成树:

  从以过程 中可以发 现,整个过程其实就是不断循环第二步和第三步,直到最后一个顶点为止。
2、完整实现代码如下:
/*********************************************************************/
/**            最小生成树之普里姆算法(邻接矩阵存储图)                   **/
/*********************************************************************/

#include <stdio.h>

#define GRAPH_MAX_VERTEX_SIZE 100           // 最大顶点数
#define INFINITY 65535                      // 用65535来代表∞,在这里代表村庄之间没有可达路径

typedef char VertexType;     // 顶点类型
typedef int EdgeType;        // 边上的权值类型

typedef struct Graph
{
	VertexType vexs[GRAPH_MAX_VERTEX_SIZE];           // 顶点表
	EdgeType arc[GRAPH_MAX_VERTEX_SIZE][GRAPH_MAX_VERTEX_SIZE];      // 邻接矩阵
	int numVertexs, numEdges;            // 图中当前的顶点数和边数
}Graph;

/**
 * 建立无向网图的邻接矩阵表示
 * @param graph:指向图结构的指针
 */
void CreateGraph(Graph *graph)
{
	int i, j, k, w;

	printf("输入顶点数和边数,分别用空格分隔:");
	scanf("%d %d", &(graph->numVertexs), &(graph->numEdges));          // 接收输入的顶点数和边数

	for(i = 0; i < graph->numVertexs; i++)                             // 读入顶点信息,建立顶点表
	{
	    printf("输入第%d个顶点信息:", i + 1);
        fflush(stdin);                                                 // 清空键盘输入缓冲区
		scanf("%c", &(graph->vexs[i]));
	}

	for(i = 0; i < graph->numVertexs; i++)
	{
		for(j = 0; j < graph->numVertexs; j++)
		{
			graph->arc[i][j] = INFINITY;	                           // 邻接矩阵初始化
		}
	}

	for(k = 0; k < graph->numEdges; k++)                               // 读入numEdges条边,建立邻接矩阵
	{
		printf("输入边(vi,vj)上的下标i,下标j和权w,分别用空格分隔:");
		fflush(stdin);                                                 // 清空键盘输入缓冲区
		scanf("%d %d %d", &i, &j, &w);
		graph->arc[i][j] = w;
		graph->arc[j][i] = graph->arc[i][j];                           // 因为是无向图,矩阵对称
	}
}


/**
 * 普里姆算法生成最小生成树
 */
void Prim(Graph graph)
{
    int min, i, j, k;

    int adjvex[GRAPH_MAX_VERTEX_SIZE];   // 保存相关顶点下标
    int lowcost[GRAPH_MAX_VERTEX_SIZE];  // 保存相关顶点之间边的权值(成本)

    adjvex[0] = 0;                       // V0第一个加入
    lowcost[0] = 0;                      // V0作为最小生成树的根开始遍历,权值为0

    // 初始化
    for(i = 1; i < graph.numVertexs; i++)
    {
        lowcost[i] = graph.arc[0][i];    // 将邻接矩阵第0行所有权值加入数组中
        adjvex[i] = 0;                   // 初始化全部先为V0的下标
    }

    // 构造最小生成树
    for(i = 1; i < graph.numVertexs; i++)
    {
        min = INFINITY;                  // 初始化最小权值为65535等不可能的数值
        j = 1;
        k = 0;

        // 遍历全部顶点
        while(j < graph.numVertexs)
        {
            // 找出lowcost数组已存储的最小权值
            if(lowcost[j] != 0 && lowcost[j] < min)
            {
                min = lowcost[j];
                k = j;                   // 将发现的最小权值的下标赋值给k
            }
            j++;
        }


        // 打印当前顶点边中权值最小的边
        printf("(%d, %d)", adjvex[k], k);
        lowcost[k] = 0;                     // 将当前顶点的权值设为0,表示此顶点已完成任务,进入下一个顶点的遍历

        // 邻接矩阵k行逐个遍历全部顶点
        for(j = 1; j <graph.numVertexs; j++)
        {
            if(lowcost[j] != 0 && graph.arc[k][j] < lowcost[j])
            {
                lowcost[j] = graph.arc[k][j];
                adjvex[j] = k;
            }
        }
    }
}

int main()
{
    Graph graph;

    CreateGraph(&graph);
    Prim(graph);
    return 0;
}

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值