【数据结构】图的最小生成树(普里姆Prim算法、克鲁斯卡尔Kruskal算法)(C语言)


一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只有足以构成一棵树的 n-1 条边。在一个连通网的所有生成树中,各边代价之和最小的那棵生成树称为该连通网 最小代价生成树(MST),简称为 最小生成树
利用 普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法可以生成一个连通网的最小代价生成树。

1. 普里姆(Prim)算法

1.1 实现步骤

基本步骤
假设N=(V,{E})是连通网,,TE为最小代价生成树中边的集合。
① 初始U={u0}(u0∈V),TE=空集;
② 在所有u∈U,v∈V-U的边中选一条代价最小的边(u0,v0)并入集合TE,同时将v0并入U;
③ 重复②,直到U=V为止。
即每次选择某一顶点的权值最小的边。
示例
Prim算法步骤

1.2 完整实现代码+注释

# include<stdio.h>
# define MAX_VERTEX_NUM 20								//最多顶点个数
# define INFINITY 32768

/*图的邻接矩阵存储结构*/
typedef char VertexData;
typedef struct {
    
    
	VertexData vertex[MAX_VERTEX_NUM];					//顶点向量
	int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];			//邻接矩阵
	int vexnum, arcnum;									//图的顶点数和弧数
}AdjMatrix;

AdjMatrix G;

/*求顶点位置函数*/
int LocateVertex(AdjMatrix* G, VertexData v) {
    
    
	int k;
	for (k = 0; k < G->vexnum; k++) {
    
    
		if (G->vertex[k] == v)
			break;
	}
	return k;
}

/*创建一个无向网*/
void CreateAdjMatrix(AdjMatrix* G) {
    
    
	int i, j, k, weight;
	VertexData v1, v2;
	printf("请输入图的顶点数和弧数:");
	scanf("%d%d", &G->vexnum, &G->arcnum);				//输入图的顶点数和弧数
	for (i = 0; i < G->vexnum; i++) {
    
    					//初始化邻接
		for (j = 0; j < G->vexnum; j++)
			G->arcs[i][j] = INFINITY;
	}
	printf("请输入图的顶点:");
	for (i = 0; i < G->vexnum; i++)
		scanf(" %c", &G->vertex[i]);					//输入图的顶点
	for (k = 0; k < G->arcnum; k++) {
    
    
		printf("请输入第%d条弧的两个顶点和权值:", k + 1);
		scanf(" %c %c %d", &v1, &v2, &weight);			//输入一条弧的两个顶点和权值
		i = LocateVertex(G, v1);
		j = LocateVertex(G, v2);
		G->arcs[i][j] = G->arcs[j][i] = weight;			//建立对称弧
	}
}

/*普利姆算法*/
void Prime(AdjMatrix G) {
    
    
	int min, i, j, k, mincost = 0;
	int adjvex[MAX_VERTEX_NUM];							//保存相关顶点下标
	int lowcost[MAX_VERTEX_NUM];						//保存相关顶点间边的权值
	lowcost[0] = 0;										//初始化第一个权值为0,即v0加入生成树
	adjvex[0] = 0;										//初始化第一个顶点下标为0
	for (i = 1; i < G.vexnum; i++) {
    
    					//循环除下标为0外的全部顶点
		lowcost[i] = G.arcs[0][i];						//将v0顶点与之有边的权值存入数组
		adjvex[i] = 0;									//初始化都为v0下标
	}
	for (i = 1; i < G.vexnum; i++) {
    
    
		min = INFINITY;									//初始化最小权值为无穷大
		j = 1;
		k = 0;
		while (j < G.vexnum) {
    
    
			if (lowcost[j] != 0 && lowcost[j] < min) {
    
    	//如果权值不为0,且权值小于min
				min = lowcost[j];						//则让当前权值成为最小值
				k = j;									//将当前最小值的下标存入k
			}
			j++;
		}
		printf("(%c,%c)", G.vertex[adjvex[k]], G.vertex[k]);	//打印当前顶点边中权值最小边
		lowcost[k] = 0;									//将当前顶点的权值设置为0,表示此顶点已经完成任务
		mincost += min;

		for (j = 1; j < G.vexnum; j++) {
    
    				//循环所有顶点
			if (lowcost[j] != 0 && G.arcs[k][j] < lowcost[j]) {
    
    
				lowcost[j] = G.arcs[k][j];
				adjvex[j] = k;							//将下标为k的顶点存入adjvex
			}
		}
	}
	printf("\n最小代价为:");
	printf("%d\n", mincost);
}

int main() {
    
    
	CreateAdjMatrix(&G);
	printf("\n最小代价生成树为:");
	Prime(G);
	return 0;
}

1.3 运行结果

运行结果1

2. 克鲁斯卡尔(Kruskal)算法

2.1 实现步骤

基本步骤
假设N=(V,{E})是连通网,将N中的边按权值从小到大的顺序排列。
① 将n个顶点看成n个集合。
② 按权值从小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集合内,将该边放到生成树边的集合中,同时将该边的两个顶点所在的顶点集合合并。
③ 重复②直到所有的顶点都在同一顶点集合内。
示例
Kruskal算法步骤① 将待选的边按权值从小到大的顺序排列,得:(B,C),5;(B,D),6;(C,D),6;(B,F),11;(D,F),14;(A,B),16;(D,E),18;(A,E),19;(A,F),21;(E,F),33。
顶点集合状态:{A},{B},{C},{D},{E},{F}。
最小生成树的边的集合:{ }。
② 从待选边中选一条权值最小的边:(B,C),5。
顶点集合状态变为:{A},{B,C},{D},{E},{F}。
最小生成树的边的集合:{(B,C)}。
③ 从待选边中选一条权值最小的边:(B,D),6。
顶点集合状态变为:{A},{B,C,D},{E},{F}。
最小生成树的边的集合:{(B,C),(B,D)}。
④ 从待选边中选一条权值最小的边:(C,D),6,由于C,D在同一顶点集合{B,C,D}内,故放弃,重新从待选边中选一条权值最小的边:(B,F),11。
顶点集合状态变为:{A},{B,C,D,F},{E}。
最小生成树的边的集合:{(B,C),(B,D),(B,F)}。
⑤ 从待选边中选一条权值最小的边:(D,F),14,由于D,F在同一顶点集合{B,C,D,F}内,故放弃,重新从待选边中选一条权值最小的边:(A,B),16。
顶点集合状态变为:{A,B,C,D,F},{E}。
最小生成树的边的集合:{(B,C),(B,D),(B,F),(A,B)}。
⑥ 从待选边中选一条权值最小的边:(D,E),18。
顶点集合状态变为:{A,B,C,D,F,E}。
最小生成树的边的集合:{(B,C),(B,D),(B,F),(A,B),(D,E)}。

至此,所有顶点都在同一顶点集合{A,B,C,D,F,E}中,算法结束,最小生成树构造完毕,最小代价为5+6+11+16+18=56。

1.2 完整实现代码+注释

# include<stdio.h>
# define MAX_VERTEX_NUM 20								//最多顶点个数
# define INFINITY 32768

typedef char VertexData;
/*边存储结构*/
typedef struct Edge {
    
    
	int u;
	int v;
	int weight;
}Edge, EdgeVec[MAX_VERTEX_NUM];

/*图存储结构*/
typedef struct EdgeGraph {
    
    
	VertexData vertex[MAX_VERTEX_NUM];	//顶点数组存储顶点信息
	EdgeVec arcs;						//弧数组存储弧信息
	int vexnum, arcnum;					//顶点数,弧数
}EdgeGraph;

/*求顶点位置函数*/
int LocateVertex(EdgeGraph* G, VertexData v) {
    
    
	int k;
	for (k = 0; k < G->vexnum; k++) {
    
    
		if (G->vertex[k] == v)
			break;
	}
	return k;
}

/*创建一个网*/
void Create(EdgeGraph* G) {
    
    
	int i, j, k, weight;
	VertexData v1, v2;
	printf("请输入图的顶点数和弧数:");
	scanf("%d%d", &G->vexnum, &G->arcnum);			//输入图的顶点数和弧数
	for (i = 0; i < G->vexnum; i++)					//初始化弧的权值
		G->arcs[i].weight = INFINITY;
	printf("请输入图的顶点:");
	for (i = 0; i < G->vexnum; i++)
		scanf(" %c", &G->vertex[i]);				//输入图的顶点
	for (k = 0; k < G->arcnum; k++) {
    
    
		printf("请输入第%d条弧的两个顶点和权值:", k + 1);
		scanf(" %c %c %d", &v1, &v2, &weight);		//输入一条弧的两个顶点和权值
		i = LocateVertex(G, v1);
		j = LocateVertex(G, v2);
		G->arcs[k].u = v1;							//建立弧
		G->arcs[k].v = v2;
		G->arcs[k].weight = weight;
	}
}

/*按权值将边从小到大排序*/
void Sort(EdgeGraph* G) {
    
    
	int i, j;
	Edge temp;
	for (i = 0; i < G->arcnum - 1; i++) {
    
    
		for (j = 0; j < G->arcnum - i - 1; j++) {
    
    
			if (G->arcs[j].weight > G->arcs[j + 1].weight) {
    
    
				temp = G->arcs[j];
				G->arcs[j] = G->arcs[j + 1];
				G->arcs[j + 1] = temp;
			}
		}
	}
}

/*克鲁斯卡尔算法*/
void Kruskal(EdgeGraph G) {
    
    
	int i, mincost = 0;
	Sort(&G);										//对边进行按权值排序
	int Vexset[MAX_VERTEX_NUM];						//Vexset数组用于记录顶点集合,下标对应顶点序号,值对应顶点集合序号
	for (i = 0; i < G.vexnum; i++)					//初始化,各顶点均处于单独的顶点集合中
		Vexset[i] = i;
	int v1, v2, vs1, vs2;
	for (int i = 0; i < G.arcnum - 1; i++){
    
    
		v1 = LocateVertex(&G, G.arcs[i].u);			//弧连接的顶点的序号
		v2 = LocateVertex(&G, G.arcs[i].v);
		vs1 = Vexset[v1];							//顶点所在顶点集合
		vs2 = Vexset[v2];
		if (vs1 != vs2){
    
    							//判断两个顶点不在同一顶点集合中
			printf("(%c,%c)", G.arcs[i].u, G.arcs[i].v);
			mincost += G.arcs[i].weight;
			for (int j = 0; j < G.vexnum; j++){
    
    
				if (Vexset[j] == vs2)				//合并顶点集合
					Vexset[j] = vs1;
			}
		}
	}
	printf("\n最小代价为:");
	printf("%d\n", mincost);
}

int main() {
    
    
	EdgeGraph G;
	Create(&G);
	printf("\n最小代价生成树为:");
	Kruskal(G);
	return 0;
}

1.3 运行结果

运行结果2
参考:耿国华《数据结构——用C语言描述(第二版)》

更多数据结构内容关注我的《数据结构》专栏https://blog.csdn.net/weixin_51450101/category_11514538.html?spm=1001.2014.3001.5482

猜你喜欢

转载自blog.csdn.net/weixin_51450101/article/details/122989240