Krushkal(克鲁斯卡尔)算法,保姆式详细讲解——最小生成树

本文详细介绍了克鲁斯卡尔算法(Kruskal's Algorithm)的思想与实现过程,通过一个无向图实例展示了如何构造最小生成树。首先从边集数组结构体出发,通过邻接矩阵初始化图,接着按权值排序边集,最后通过判断环路避免原则逐步选择边构建最小生成树。代码中包含关键函数如Find_com用于查找顶点所属集合,以及Mintree_Create_Kruskal函数用于实际构建最小生成树。

算法思想很简单,比prim算法要清晰很多,难处可能还是在具体实现上面。

思想既然要找最小生成树,让各个选中的边的权值加起来最小,那我们是不是可以很自然地想到我们先找到一个权值最小的边,然后找到第二小的边......这样就能出现最小的边的权值和了。就好像我们去迪士尼,排队很烦人,但是我们为了尽可能玩多的项目,就先去排队最小的项目然后去排队人数第二小的项目......

于是我们就可以从边出发建立最小生成树了。但是,哪有那么简单的算法呢哈哈哈,它还有一个难处就是,我们都知道图的定义和树的定义里面,我们是不研究“环”的,我们在选择边的时候很可能就会选出一个环来,那么就不符合树的结构了,这就不对了,于是我们要在选的时候进行判断,如果这个边选入会构成环,那么就不要,我们看下一条边。

那么问题又来了,这个边怎么选呢?你怎么把最小权值的边找出来呢?这里就要用到边集数组,把边的信息全部放到一个数组里面,就可以了。

下面是图解:

先有一个无向图(画的丑勿怪.....)

 然后我们按照上文所说,先找出最小两条的边选入最小生成树,用蓝色标起来。就是 5,7这两条

 然后选第三条:

 然后关键来了,我们选第四条,按照大小应该选9这一条,可是,我们会发现,如果选了9 ,那么7,9,5这三条边就构成了环,那就不行了,所以9这条不选,继续往下看,找到10,可以选

 然后选15,然后选18,然后是21,诶,如果选了就有环,所以不选

 然后选23,也不行。29也不行,所以就到此为止,我们的最小生成树就出来了。

下面给出具体实现,几乎每行代码都有注释,大家可以通过注释,可以更好理解他的实现。

PS:其中用到的邻接矩阵等图的储存知识,可以看看这个图(graph)的基础知识详解_chy响当当的博客-CSDN博客

#include"bits/stdc++.h"
using namespace std;
const int INFINITE = 1000;
//先定义边集数组结构体
typedef struct {
	int begin;//边的起始,但是由于我们举的例子是无向图的邻接矩阵,所以起始末尾大家可以随意
	int end;//边的末尾,不过如果你想做有向图的边集数组的话,需要处理一下
	int data;//边的权值
}Edge;

//那么在开始之前,我们也需要一个把无向图变成边集数组的函数
//根据上面的讲解,边集数组是把边和边的权值存入,我们这里有需要对边的权值按大小排序
//所以无非就是循环然后找出最小的边存入,然后第二小的......

typedef struct
{
	int vex[50];
	int arc[50][50];
	int numvex;
	int numarc;
}Graph;//邻接矩阵,


//然后简单初识化一下,就直接确定相应的权值了

void CreateMGraph(Graph* G)
{
	int i, j;

	// printf("请输入边数和顶点数:");/
	G->numarc = 15;
	G->numvex = 9;

	for (i = 0; i < G->numvex; i++)/* 初始化图 */
	{
		for (j = 0; j < G->numvex; j++)
		{
			if (i == j)
				G->arc[i][j] = 0;
			else
				G->arc[i][j] = G->arc[j][i] = INFINITE;//没有边的就初始化为不可能值,
		}
	}

	G->arc[0][1] = 10;
	G->arc[0][5] = 11;
	G->arc[1][2] = 18;
	G->arc[1][8] = 12;
	G->arc[1][6] = 16;
	G->arc[2][8] = 8;
	G->arc[2][3] = 22;
	G->arc[3][8] = 21;
	G->arc[3][6] = 24;
	G->arc[3][7] = 16;
	G->arc[3][4] = 20;
	G->arc[4][7] = 7;
	G->arc[4][5] = 26;
	G->arc[5][6] = 17;
	G->arc[6][7] = 19;

	for (i = 0; i < G->numvex; i++)
	{
		for (j = i; j < G->numvex; j++)
		{
			G->arc[j][i] = G->arc[i][j];
		}
	}
}

然后处理边集数组:

//而循环又需要用到图的循环,可以使用图的广度优先遍历等等(以后有机会我可以专门做一个遍历·讲解)
// 交换权值 以及头和尾 
void Swapn(Edge* edges, int i, int j)
{
	int temp;
	temp = edges[i].begin;
	edges[i].begin = edges[j].begin;
	edges[j].begin = temp;
	temp = edges[i].end;
	edges[i].end = edges[j].end;
	edges[j].end = temp;
	temp = edges[i].data;
	edges[i].data = edges[j].data;
	edges[j].data = temp;
}
void Create_Egde_array(Edge a[],Graph *G)//传递一个还未初始化的数组和无向图的邻接矩阵
{
	//先把无向数据读入,他的矩阵是对称的,所以读上三角形
	int p = 0;
	for(int i=0;i<G->numvex;i++)
		for (int j = i; j < G->numvex; j++)
		{
			if (G->arc[i][j] != INFINITE)//只要存在这条边
			{
				a[p].begin = i;
				a[p].end = j;
				a[p].data = G->arc[i][j];//这个就是权值了
				p++;
			}
		}
	//然后大小排序,就要用上面的交换了
	for(int i=0;i<G->numvex;i++)
		for (int j = i + 1; i < G->numvex; j++)
		{
			if (a[i].data > a[j].data)
			{
				Swapn(a, i, j);
			}
		}
	//可以检查一下对不对
	for (int i = 0; i < G->numarc; i++)
	{
		printf("(%d, %d) %d\n", a[i].begin, a[i].end, a[i].data);
	}
}

接下来就是重点:KRUSKAL算法

int Find_com(int* parent, int vex);
void Mintree_Create_Kruskal(Graph G,Edge edge[])
{
	int parent[50];//这个是用于判断是否形成回路的,我们可以想象它里面有一些传递的关系,如果你
	//传递的两个顶点下标,这两个下标,在这个数组里面可以传递到一个数字,那说明就构成了一个环,
	//具体可以看下面的Find_com函数

	for (int i = 0; i < G.numvex; i++)
		parent[i] = 0;//全部初识化为0,这个可以自己设置,只要能在自己心中,和顶点下标有区分即可
	for (int i = 0; i < G.numarc; i++)
	{
		int n = Find_com(parent, edge[i].begin);
		int m = Find_com(parent, edge[i].end);//因为edge这个边集数组已经按大小排好序了,从0开始就是从最小边开始
	//然后根据Find_com的注释
		if (n != m)
		{
			parent[n] = m;//parent[m]=n也一样,反正是无向的
			//然后就可以把他添加到树里面,但是树这里要给还得添很多与今天重点无关的代码,
			//所以大家只要知道在这里,就是把边填入最小生成树的时候就行了
			//也可以Printf一下都可以,我这里就略过了
			//.......
		}
	}
}

int Find_com(int* parent, int vex)//传递一个parent数组,和当前要选的边的一个下标
{
	while (parent[vex] > 0)//parent存的是什么呢?就是对应的下标。比如顶点1和顶点2有边,也被选入了,
		//那么我们就经过上面的处理得到一个parent[1]=2,就是说1和2有边,他们相连
		//一次类推,2和3连那么,parent[2]=3,
		//这样子如果我们在上面判断时,加入一条顶点4和顶点5连的边,parent[4]可以经过一个个传递得到3
		//而parent[5]经过传递也得到了一个3,那么显然就构成一个环了,我们就得社区4,5这条边
	{
		vex = parent[vex];//
	}

	return vex;
}

好了,今天就到这里,希望大家能明白这个算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值