C语言无向图最小生成树Prim普利姆算法

目录

一、普利姆算法的实现定义

 二、代码讲解

三、总体代码及运行实例


一、普利姆算法的实现定义

假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

在所有u∈U,v∈(V-U)的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

 二、代码讲解

无向图以邻接矩阵的形式存储:

该邻接矩阵中除了边外的所有值都被记为无限INFINITY,例如:假设V1、V2间无边,矩阵中(V1,V2)会被记为INFINITY(这是显然的),而(V1,V1)和(V2,V2)也会被记为INFINITY。

typedef char VertexType;    // 顶点类型 
typedef int EdgeType;    // 边上的权值数据类型 
#define MAXVEX 30     // 最大顶点数 
#define INFINITY 65535    // 无限,边矩阵中填入INIFINITY表示两顶点间无边 

typedef struct Graph
{
    VertexType vertex[MAXVEX];    // 顶点数组 
    EdgeType arc[MAXVEX][MAXVEX];    // 边数组,数组中的值为权 
    int numVertex, numEdge;    // 当前顶点数和边数 
}Graph; 

算法如下:

int LengthVex(int vex[])	// 返回当前vex数组的长度 
{
	int i, num=0;
	for(i=0; i<MAXVEX; i++)
	{
		if(vex[i] != INFINITY)
			num++;
	}
	
	return num;
}

int FindInVex(int vex[], int e)	// 查看vex数组中是否有元素e,有则返回1,无则返回0 
{
	int i;
	for(i=0; i<MAXVEX; i++)
	{
		if(vex[i] == e)
			return 1;
	}
	return 0;
}

void MiniSpanTree_Prim(Graph *G)	// Prim算法最小生成树 
{
	int minEdge; 	// 记录权值最小的边 
	int vex[MAXVEX];	// 记录已连入生成树的顶点下标,数组下标表示先后顺序 
	int i, j, k=0;
	int vexBegin, vexEnd;	// 边起点和边终点 
	
	for(i=0; i<MAXVEX; i++)	// 将数组中的值全部初始化为无限 
		vex[i] = INFINITY;
	vex[0] = 0;	// 从顶点v0开始生成树,也可给函数增加一个形参以从指定顶点开始生成树
	
	while(k < LengthVex(vex))
	{
		minEdge = INFINITY;
		for(i=0; i < LengthVex(vex); i++)
			for(j=0; j < G->numVertex; j++)
				if(G->arc[ vex[i] ][j] < minEdge && !FindInVex(vex, j) ) 
                // 找还未被并入最小生成树的权值最小边 
				{
					minEdge = G->arc[ vex[i] ][j];	// 记录权值 
					vexBegin = vex[i];	// 记下边的起点和终点 
					vexEnd = j;
				}
				
		k++;
		if(minEdge != INFINITY)
		{
			vex[k] = vexEnd;	// 新顶点加入生成树 
			printf("edge:(%d, %d) weight:%d \n", vexBegin, vexEnd, minEdge);
		}
	}
}

代码参考了《大话数据结构》,但个人认为书中代码使用的一维数组adjvex[]不太直观也略微有些难以理解,所以做了一些改变。对初学者来说应该会容易理解一点吧?大概。

代码中的vex[]数组用于记录已经连入最小生成树,或者说已经从集合(U-V)中并入集合U的顶点,vex[]数组中的值就是这些顶点的下标。

vexBegin和vexEnd仅用于在循环过程中记下权值最小边的起始顶点和结束顶点,在循环结束后打印输出。虽然定义了vexBegin和vexEnd,但图仍然是无向图哦。

算法的核心部分:

while(k < LengthVex(vex))
	{
		minEdge = INFINITY;    // 在上次循环中minEdge可能记下了一个最小权值,
                               // 所以要在新循环开始时重新将其初始化为无限                            
		for(i=0; i < LengthVex(vex); i++)
			for(j=0; j < G->numVertex; j++)
				if(G->arc[ vex[i] ][j] < minEdge && !FindInVex(vex, j) )  
                // 找还未被并入最小生成树的权值最小边 
				{
					minEdge = G->arc[ vex[i] ][j];	// 记录权值 
					vexBegin = vex[i];	// 记下边的起点和终点 
					vexEnd = j;
				}
				
		k++;  // 与LengthVex(vex)配合在合适的时候结束while循环

	    // minEdge==INFINITY表示所有能连入生成树的顶点都已经连入,总体while循环应该结束 
        // 当minEdge==INFINITY时数组vex[]不再有新元素加入,LengthVex(vex)值不再增加
        // 此时while循环条件不再满足,循环终止
        if(minEdge != INFINITY)
		{
			vex[k] = vexEnd;	// 新顶点加入生成树,LengthVex(vex)值会相应增加
			printf("edge:(%d, %d) weight:%d \n", vexBegin, vexEnd, minEdge);
		}
	}

这一部分代码是对本文开头Prim算法实现定义的直观构造,数组vex[]即定义中的集合U,嵌套的for()循环就是 不断寻找起点在集合U,终点在集合(U-V)的最小权值边,然后将该边所连接的新顶点并入集合U的过程。

在这一过程中U从{V0}开始不断有新顶点并入,直到图中的所有顶点都被并入U。每当有一个顶点被并入U,就有一条边被选中,U从{V0}开始有n-1个顶点并入,则也有n-1条边被选中,被并入边集合TE(当然代码中没有边被并入的直观表示)。

三、总体代码及运行实例

#include<stdio.h>
#include<stdlib.h>

typedef char VertexType;	// 顶点类型 
typedef int EdgeType;	// 边上的权值数据类型 
#define MAXVEX 30 	// 最大顶点数 
#define INFINITY 65535	// 无限,边矩阵中填入INIFINITY表示两顶点间无边 

typedef struct Graph
{
	VertexType vertex[MAXVEX];	// 顶点数组 
	EdgeType arc[MAXVEX][MAXVEX];	// 边数组,数组中的值为权 
	int numVertex, numEdge;	// 当前顶点数和边数 
}Graph; 

void CreateGraph(Graph *G)	// 创建无向图的邻接矩阵表示 
{
	int i,j,k,w;
	printf("依次输入顶点数和边数:(数值间用“,”分隔)\n");
	scanf("%d,%d", &(G->numVertex), &(G->numEdge));
	getchar();	// 由于scanf()从缓冲区读取字符,
				// 需要用getchar()吃掉上一个scanf()末尾键入的回车 
	printf("依次输入各顶点:\n");
	for(i=0; i < G->numVertex; i++)
		scanf("%c", &(G->vertex[i]));

	for(i=0; i < G->numVertex; i++)		// 边矩阵中的值全部初始化为无限,表示图中不存在边 
		for(j=0; j < G->numVertex; j++)
			G->arc[i][j] = INFINITY;
			
	for(k=0; k < G->numEdge; k++)	// 向边矩阵中填入边的权值 
	{
		printf("输入边(vi,vj)的下标i,j与边的权w:(数值间用“,”分隔)\n");
		scanf("%d,%d,%d", &i, &j, &w);
		G->arc[i][j] = w;
		G->arc[j][i] = G->arc[i][j];	// 无向图的边矩阵为对称矩阵 
	}
}

int LengthVex(int vex[])	// 返回当前vex数组的长度 
{
	int i, num=0;
	for(i=0; i<MAXVEX; i++)
	{
		if(vex[i] != INFINITY)
			num++;
	}
	
	return num;
}

int FindInVex(int vex[], int e)	// 查看vex数组中是否有元素e,有则返回1,无则返回0 
{
	int i;
	for(i=0; i<MAXVEX; i++)
	{
		if(vex[i] == e)
			return 1;
	}
	return 0;
}

void MiniSpanTree_Prim(Graph *G)	// Prim算法最小生成树 
{
	int minEdge; 	// 记录权值最小的边 
	int vex[MAXVEX];	// 记录已连入生成树的顶点下标,数组下标表示先后顺序 
	int i, j, k=0;
	int vexBegin, vexEnd;	// 边起点和边终点 
	
	for(i=0; i<MAXVEX; i++)	// 将数组中的值全部初始化为无限 
		vex[i] = INFINITY;
	vex[0] = 0;	// 从顶点v0开始生成树
	
	while(k < LengthVex(vex))
	{
		minEdge = INFINITY;
		for(i=0; i < LengthVex(vex); i++)
			for(j=0; j < G->numVertex; j++)
				if(G->arc[ vex[i] ][j] < minEdge && !FindInVex(vex, j) )  
                // 找还未被并入最小生成树的权值最小边 
				{
					minEdge = G->arc[ vex[i] ][j];	// 记录权值 
					vexBegin = vex[i];	// 记下边的起点和终点 
					vexEnd = j;
				}
				
		k++;
		if(minEdge != INFINITY)
		{
			vex[k] = vexEnd;	// 新顶点加入生成树 
			printf("edge:(%d, %d) weight:%d \n", vexBegin, vexEnd, minEdge);
		}
	}
}

int main()
{
	Graph *G = (Graph*)malloc(sizeof(Graph));
	CreateGraph(G);
	
	MiniSpanTree_Prim(G);
	
}

输入示例及运行结果:

        输入的图为:

        该图的最小生成树应为:

         运行结果(前面为构造图的输入,后面为最小生成树的输出结果):

         显然运行结果符合最小生成树,程序正确运行。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值