最小生成树之克鲁斯卡尔算法

前面一篇博客讲的prim算法最小生成树之普里姆算法是以某一个顶底为起点,逐步找个顶点上权值最小的边来构建最小生成树的,既然我们的权值在边上,那么我们为何不根据边 来构建最小生成树呢?这就是克鲁斯卡尔算法。老规矩,先上代码再讲解:

typedef char VertexType;     //顶点类型,可根据需要修改
typedef int EdgeType;   //边上的权值类型,可根据需要修改
#define MAXVEX 100   //最大定点数,应由实际情况定
#define MAXEDGE 15  //边数量的最大值,根据实际情况而定,这里定的15和后面讲的例子有关
#define INFINITY 65535    //代表∞

typedef struct
{
	char vexs[100];   //定点表
	int arc[100][100];   //邻接矩阵,可看做边表
	int numVertexes, numEdges;  //图中当前的顶点数和边数
} MGraph;

typedef struct    //边集数组
{
	int begin;  //起点下标
	int end;  //终点下标
	int weight;  //权值
}Edge

/* 交换权值 以及头和尾 */
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].weight;  //交换权值
	edges[i].weight = edges[j].weight;
	edges[j].weight = temp;
}

/* 对权值进行排序 */
void sort(Edge edges[],MGraph *G)
{
	int i, j;
	for ( i = 0; i < G->numEdges; i++)
	{
		for ( j = i + 1; j < G->numEdges; j++)
		{
			if (edges[i].weight > edges[j].weight)
			{
				Swapn(edges, i, j);   
			}
		}
	}
}

/*查找连线定点的尾部下标*/
int  Find(int *parent, int f)
{
	while(parent[f] > 0)
		f = parent[f];
	return f;
}

/*克鲁斯卡尔算法*/
void MiniSpanTree_Kruskal(MGraph G)
{
	int i, n, m;
	Edge edges[MAXEDGE];
	int parent[MAXVEX];

	for ( i = 0; i < G.numVertexes-1; i++)   //用邻接矩阵来构建边集数组并排序
	{
		for (j = i + 1; j < G.numVertexes; j++)
		{
			if (G.arc[i][j]<INFINITY)
			{
				edges[k].begin = i;
				edges[k].end = j;
				edges[k].weight = G.arc[i][j];
				k++;
			}
		}
	}
	sort(edges, &G);
	
	for (i = 0; i < G.numVertexes; i++)
		parent[i] = 0;	// 初始化数组值为0 

	printf("打印最小生成树:\n");
	for (i = 0; i < G.numEdges; i++)	  // 循环每一条边 
	{
		n = Find(parent,edges[i].begin);
		m = Find(parent,edges[i].end);
		if (n != m)       // 假如n与m不等,说明此边没有与现有的生成树形成环路 
		{
			parent[n] = m;   	// 将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中 
			printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
		}
	}
}

代码有点长,不过没关系,不怕,慢慢走就行了。
咱们的主菜是克鲁斯卡尔算法,所以直接来看MiniSpanTree_Kruskal()函数。首先是一堆定义,记住parent[]数组,后面要考的。咱们遇到的第一个是两个for循环的嵌套,其目的是将我们的邻接矩阵转换成边集数组。先存进edges对应的项中,等存完后(几条边就几项)就排序,再来看看sort()函数,是根据权值的大小进行排序的,交换的时候用到Swapn()函数,没事儿,进去看看。进去后发现,原来就是每项都交换一下,我们仍然以我上一篇博客里的图为例,edges[]数组如下:
在这里插入图片描述
继续向后走,碰到一个用来初始化parent数组的for循环,初始化parent[]所有成员为0,其作用到后面会详细说,继续向下走,野生的for循环又出现了。先是算n,跳到Find()函数去执行,由于所有的parent[]都是0,所以直接返回了edges[0].begin = 4,所以n = 4,同理,m = 7。一看m != n啊,执行if里的内容,将终点下标赋值给下标为起点的parent[]中,打印(4,7)7。

for (i = 0; i < G.numEdges; i++)	 //循环每一条边 
{
	n = Find(parent,edges[i].begin);
	m = Find(parent,edges[i].end);
	if (n != m)     // 假如n与m不等,说明此边没有与现有的生成树形成环路 
	{
		parent[n] = m;	  // 将此边的结尾顶点放入下标为起点的parent中。表示此点已经在生成树集合中 
		printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
	}
}

此时的parent[] = {0, 0, 0, 0, 7, 0, 0, 0, 0};
我们已经将边(V4, V7)放入最小生成树中了(如下图左):
在这里插入图片描述
接着循环执行上述步骤,得到打印结果(2, 8)8,parent[2] = 8;即(V2, V8)也被树纳入其中
此时parent[] = {0, 0, 8, 0, 7, 0, 0, 0, 0}(上图右)
再来,得到(0, 1)10,parent[0] = 1;即(0, 1)也被放进树中
此时parent[] = {1, 0, 8, 0, 7, 0, 0, 0, 0}
这是你该发现了,每次都是寻找的剩下的所有边中寻找权值最小的那个,一直执行到最后,打印的如下所示:(v0,v5)11,(v1, v8)12, (v3,v7)16,(v1, v6)16。
同时,parent[] = {1, 5, 8, 7, 7, 8, 0, 0, 6};具体变换过程如下图:

在这里插入图片描述可以看到此时的网就被分割成两个连通的边集合了,记做A和B(上图网6)。现在来看看parent[]数组的意义,当parent[0] = 1时,表示v0和v1已经在生成树集合A中,此时将parent[0] = 1中的1改为下标,即查找parent[1],一看是5,表示v1和v5是在一个生成树集合中(也就是A中)。如此往复,parent[5] = 8,parent[8] = 6, parent[6] = 0,集合A到此为止,至此A中已经有v0,v1,v5,v8,v6。再查看parent[]中没有查看的值,发现其中的parent[2] = 8,表示v2和v8是在同一个生成树集合中,因此在A中再加上v2。parent[3] = 7, parent[4] = 7,parent[7] = 0表示v3,v4,v7是在一个生成树集合中,我们记做是B,这样的两个集合可不行,我们最后要的是一个,也就是说parent[]数组里只能有一个0,表示所有的点都连通了,如果没有0不就是一个环了吗。

讲完了parent[]的作用,我们继续最后一步,此时的i已经运行到7了,我们进去看看是什么,edges[7].begin = 5,进入Find(),parent[5] = 8, parent[8] = 6, parent[6] = 0,所以返回6,即n = 6,再看m,传入edges[7].end = 6,进入Find(),parent[6] = 0,返回6,m = 6,此时m = n,也就是告诉我们不能讲v5和v6之间的边加进去,不然就形成环路了,继续下一波循环,i = 8。
直接看edgs[8]的begin和end,看到是v1和v2,也不能连,理由同上,没事,继续循环,i = 9。
将edges[9].begin = 6传入Find(),parent[6] = 0,所以n = 6;将edges[9].end = 7传入Find(),parent[7] = 0,n = 7,好了最后一条路找到了,打印出来(v6,v7)19,此时parent[] = {1, 5, 8, 7, 7, 8, 7, 0, 6}只剩一个parent[]是0,不管怎么循环,下面的都只能是m = n = 7。至此就结束了:
在这里插入图片描述
克鲁斯卡尔针对的是边,每次都是寻找最短的,这也是贪心的一个例子,其时间复杂度为O(nlogn),由于是针对边,所以比较适用于稀疏图,即点多边少的情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值