最小生成树(普里姆算法)

这个算法不好理解,我也是在csdn上看了一个博主写的文章才稍微理解了一点,需要的请移步这位博主的博客最小生成树Prim算法理解,写的非常好,很容易理解。我这里纯粹就是记录一下加深自己的印象,不懂的真的可以去看看这位博主的解释。
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。这一篇主要记录普里姆算法。普里姆算法的大概思路是,从带权连通图的任意顶点开始,如顶点A,将该顶点放入集合V中,然后从与A相连的顶点中找到一个顶点B,使得边的权值最小,将B也放入集合V中,边(A,B)就是最小生成树的一条边。接着再 从顶点中找到顶点C使得C到集合V的边权值最小(即到A或B的权值最小)。按照这种步骤继续下去,直到所有的顶点都被访问过。
普里姆算法的核心代码如下:

void prime(MGraph G)
{
		//用的无向图,说起点终点是为了好描述

	//mst[i]:表示对应lowcost[i]的起点,
	//即说明边<mst[i],i>是MST(最小生成树)的一条边,
	//当mst[i]=0表示起点i加入MST
	int mst[MAXSIZE];
	//表示以i为终点的边的最小权值,
	//当lowcost[i]=0说明以i为终点的边的最小权值=0,
	//也就是表示i点加入了MST
	int lowcost[MAXSIZE];
	lowcost[0] = 0;
	mst[0] = 0;
	//初始化lowcost为邻接矩阵第一行的权值
	//mst全部存放下标为0的顶点
	for (int i = 1; i < G.numvertex; i++)
	{
		lowcost[i] = G.edges[0][i];
		mst[i] = 0;
	}

	
	//找到第i行权值最小的边的下标
	
	for (int i = 1; i < G.numvertex; i++)
	{
		int min = INFINITY;//最小值先初始化为65535,代表无连接
		int k = 0;//k为权值最小的边的终点
		for (int j = 1; j < G.numvertex; j++)
		{
			//找到第i行权值最小的边,记录终点的下标
			if (lowcost[j]!=0&&lowcost[j]<min)
			{
				min = lowcost[j];
				k = j;
			}
		}
		
		lowcost[k] = 0;
		//打印当前顶点中权值最小的边
		printf("(%d,%d)", mst[k], k);
		//遍历第k行的所有权值
		for (int j = 1; j < G.numvertex; j++)
		{
			if (lowcost[j] != 0 && G.edges[k][j] < lowcost[j])
			{
				lowcost[j] = G.edges[k][j];
				mst[j] = k;
			}
		}
	}
	
}

我们用lowcost[i]存放以i为终点的边的最小权值,mst[i]:表示对应lowcost[i]的起点,在代码中我们从第0个顶点开始访问(其实从任何一个顶点访问最后的生成树都是一样的),以下图为例
在这里插入图片描述
邻接矩阵如下
在这里插入图片描述
我们首先访问了第0个顶点,将其放入集合V中(mst[0]=0就表示将其放入了集合V中)并将lowcost初始化为邻接矩阵第0行的值,将mst全部初始化为以0为起点。(不用特别在意初始化,我们这里这样初始化,是因为第一步就要开始找到顶点0的最小的权值,所以将lowcost初始化为邻接矩阵第一行,mst初始化为0,意味此时全部以0为起点,寻找权值最小的终点)
lowcost[1]=10 lowcost[2]=∞ lowcost[3]=∞ lowcost[4]=∞ lowcost[5] =11 lowcost[6] =∞
lowcost[7] =∞ lowcost[8] =∞
mst[1]=0 mst[2]=0 mst[3]=0 mst[4]=0 mst[5]=0 mst[6]=0 mst[7]=0 mst[8]=0
可以发现lowcost[1]的值是最小的,此时将顶点1放入集合V(只是为了好解释,代码中并没有做这件事),lowcost[1]=0;可以打印出这条边<mst[1],1>;
在这里插入图片描述
然后继续寻找到顶点0或1权值最小的点,首先我们要将lowcost现在存放的值与顶点为1的边的权值进行比较,将lowcost的值换成较小的权值,响应的也要替换mst[i]。
lowcost[1]=0 lowcost[2]=18 lowcost[3]=∞ lowcost[4]=∞ lowcost[5] =11 lowcost[6] =16
lowcost[7] =∞ lowcost[8] =12
mst[1]=0 mst[2]=1 mst[3]=0 mst[4]=0 mst[5]=0 mst[6]=1 mst[7]=0 mst[8]=1
注:mst[i]=j表示此时lowcost[i]中存放的是顶点j到顶点i的权值
可以看出此时lowcost[5]是最小的,此时mst[5]=0,意味着起点为0,终点为5这条边,将顶点5放入集合V,lowcost[5]=0 打印出边<mst[5],5>。
在这里插入图片描述
然后继续寻找到顶点0、1、5权值最小的点,首先我们要将lowcost现在存放的值与顶点为1的边的权值进行比较,将lowcost的值换成较小的权值
lowcost[1]=0 lowcost[2]=18 lowcost[3]=∞ lowcost[4]=26 lowcost[5] =0 lowcost[6] =16
lowcost[7] =∞ lowcost[8] =12
mst[1]=0 mst[2]=1 mst[3]=0 mst[4]=5 mst[5]=0 mst[6]=1 mst[7]=0 mst[8]=1
可以看出此时lowcost[8]是最小的,此时将mst[8]=1,意味着起点为1,终点为8这条边,将顶点8放入集合V, lowcost[8]=0,打印边<mst[8],8>。

在这里插入图片描述
继续
lowcost[1]=0 lowcost[2]=8 lowcost[3]=21 lowcost[4]=26 lowcost[5] =0 lowcost[6] =16
lowcost[7] =∞ lowcost[8] =0
mst[1]=0 mst[2]=8 mst[3]=8 mst[4]=5 mst[5]=0 mst[6]=1 mst[7]=0 mst[8]=1
在这里插入图片描述

lowcost[1]=0 lowcost[2]=0 lowcost[3]=21 lowcost[4]=26 lowcost[5] =0 lowcost[6] =16
lowcost[7] =∞ lowcost[8] =0
mst[1]=0 mst[2]=8 mst[3]=8 mst[4]=5 mst[5]=0 mst[6]=1 mst[7]=0 mst[8]=1
在这里插入图片描述

lowcost[1]=0 lowcost[2]=0 lowcost[3]=21 lowcost[4]=26 lowcost[5] =0 lowcost[6] =0
lowcost[7] =19 lowcost[8] =0
mst[1]=0 mst[2]=8 mst[3]=8 mst[4]=5 mst[5]=0 mst[6]=1 mst[7]=6 mst[8]=1
在这里插入图片描述

lowcost[1]=0 lowcost[2]=0 lowcost[3]=16 lowcost[4]=7 lowcost[5] =0 lowcost[6] =0
lowcost[7] =0 lowcost[8] =0
mst[1]=0 mst[2]=8 mst[3]=7 mst[4]=7 mst[5]=0 mst[6]=1 mst[7]=6 mst[8]=1
在这里插入图片描述

lowcost[1]=0 lowcost[2]=0 lowcost[3]=16 lowcost[4]=0 lowcost[5] =0 lowcost[6] =0
lowcost[7] =0 lowcost[8] =0
mst[1]=0 mst[2]=8 mst[3]=7 mst[4]=7 mst[5]=0 mst[6]=1 mst[7]=6 mst[8]=1
在这里插入图片描述

完整代码如下:

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

#define MAXSIZE 100
#define INFINITY 65535 //表示权值很大,意味着两个顶点之间没有连接

typedef struct Graph
{
	char vertex[MAXSIZE];
	int edges[MAXSIZE][MAXSIZE];
	int numvertex, numdeges;//顶点和边的个数
}MGraph;

//生成网(带权的图),邻接矩阵的方式
void  CreateGraph(MGraph *G)
{
	printf("请输入顶点和边的个数:\n");
	scanf("%d%d", &G->numvertex, &G->numdeges);
	printf("\n请输入顶点:\n");
	getchar();//如果不写这个,在输入顶点的时候总是多了一个换行符

	for (int i = 0; i < G->numvertex; i++)
	{
		scanf("%c", &G->vertex[i]);
	}
	//初始化权值为65535,表示没有连接
	for (int i = 0; i < G->numvertex; i++)
	{
		for (int j = 0; j < G->numvertex; j++)
		{
			G->edges[i][j] = INFINITY;
		}
	}

	
	for (int k = 0; k < G->numdeges; k++)
	{
		printf("请输入边(vi,vj)的下标,i,j和权值w:\n");
		int i, j, w;
		scanf("%d%d%d", &i, &j, &w);
		G->edges[i][j] = w;
		G->edges[j][i] = w;//无向网图是对称的
	}
}

void prime(MGraph G)
{
	//用的无向图,说起点终点是为了好描述

	//mst[i]:表示对应lowcost[i]的起点,
	//即说明边<mst[i],i>是MST(最小生成树)的一条边,
	
	
	int mst[MAXSIZE];
	//表示以i为终点的边的最小权值,
	//当lowcost[i]=0说明以i为终点的边的最小权值=0,
	//也就是表示i点加入了MST
	int lowcost[MAXSIZE];
	lowcost[0] = 0;
	mst[0] = 0;
	//初始化lowcost为邻接矩阵第一行的权值
	//mst全部存放下标为0的顶点
	for (int i = 1; i < G.numvertex; i++)
	{
		lowcost[i] = G.edges[0][i];
		mst[i] = 0;
	}

	
	//找到第i行权值最小的边的下标
	
	for (int i = 1; i < G.numvertex; i++)
	{
		int min = INFINITY;//最小值先初始化为65535,代表无连接
		int k = 0;//k为权值最小的边的终点
		for (int j = 1; j < G.numvertex; j++)
		{
			//找到第i行权值最小的边,记录终点的下标
			if (lowcost[j]!=0&&lowcost[j]<min)
			{
				min = lowcost[j];
				k = j;
			}
		}
		
		lowcost[k] = 0;
		//打印当前顶点中权值最小的边
		printf("(%d,%d)", mst[k], k);
		//遍历第k行的所有权值
		for (int j = 1; j < G.numvertex; j++)
		{
			if (lowcost[j] != 0 && G.edges[k][j] < lowcost[j])
			{
				lowcost[j] = G.edges[k][j];
				mst[j] = k;
			}
		}
	}
	
}

int main()
{
	MGraph G;
	CreateGraph(&G);
	prime(G);
	
	return 0;
}

对于文章开头的那张图以及对应的邻接矩阵 最后输出的结果是(0,1)(0,5)(1,8)(8,2)(1,6)(6,7)(7,4)(7,3)

代码一发上来就成这种让人头疼的格式了,只能凑合看了,实在是对编辑器不熟悉,刚开始写博客。

猜你喜欢

转载自blog.csdn.net/junya_zhang/article/details/83544709
今日推荐