C数据结构与算法-基础整理-图-06:克鲁斯卡尔算法详解

对着背影说爱祢 提交于 2020-02-19 04:08:47
详解最小生成树中的克鲁斯卡尔算法

0x01.关于克鲁斯卡尔算法

Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表。克鲁斯卡尔算法主要针对边集数组展开。

0x02.基础代码

这个算法主要是针对边集数组,先来看一下边集数组的结构:

//边集数组
typedef struct
{
	int begin;
	int end;
	int weight;
}Edge;

通常用到邻接矩阵,所以还需要由邻接矩阵转化为边集数组。另外这个算法还需要按照边的权值升序排序。

void OperationEdge(Graph G, Edge* edges)
{
	int i, j,k;
	k = 0;
	for (i = 0; i < G.numv; i++)
	{
		for (j = i+1; j < G.numv; j++)//只需要转化邻接矩阵的一半,无向图
		{
			if (G.edge[i][j] != INTMAX && G.edge[i][j] != 0)
			{
				edges[k].begin = i;
				edges[k].end = j;
				edges[k].weight = G.edge[i][j];
				k++;
			}
		}
	}
	for (i = 0; i < k-1; i++)//简单交换排序
	{
		for (j = i + 1; j < k; j++)
		{
			if (edges[i].weight > edges[j].weight)
			{
				Edge tmp;
				tmp = edges[i];
				edges[i] = edges[j];
				edges[j] = tmp;
			}
		}
	}
}

克鲁斯卡尔算法代码(简单版):

void MinSpanTree_Kruskal(Graph G)
{
	int i, n, m;
	Edge edges[MAXSIZE];//定义边集数组
	int parent[MAXSIZE];//用于判断树是否会成环
	OperationEdge(G, edges);//邻接矩阵向边集数组转换,并升序排序
	for (i = 0; i < G.numv; i++)//初始化
	{
		parent[i] = 0;
	}
	int cou = 0;//计数器,记录实际上已加入生成树的边数
	for (i = 0; i < G.nume; i++)
	{
		if (cou == G.numv - 1)//加入生成树的边等于顶点数-1,已经完成了最小生成树的建立,退出循环
		{
			printf("最小生成树构造完成!!!");
			break;
		}
		n = Find(parent, edges[i].begin);
		m = Find(parent, edges[i].end);
		if (n != m)//没有成环,加入生成树
		{
			cou++;
			parent[n] = m;
			printf("边 %c到%c ,权值为 %d ,已加入生成树", G.ver[edges[i].begin], G.ver[edges[i].end], edges[i].weight);
		}
	}
}

0x03.过程详解

首先看一个最原始的图:

再来看一下边集数组中的值:

开始模拟运行:

第一次循环,由于parent数组内容都为0,所以进入Find函数后之间返回,n=4,m=7,此时n于m不相等。parent[4]=7,边E--H加入生成树。(具体parent如何发挥作用看下文)

第二次循环,0进入Find函数后之间返回,n=0,1也立即返回,m=1,此时parent[0]=1,边A--B加入生成树。

第三次循环,0进入Find函数后,由于Find[0]=1,所以开始再一次小循环,f=parent[0]=1,parent[1]=0,退出小循环,所以返回1,n=1,5进入Find函数后,立即返回,m=5,parent[1]=5,边A--F加入生成树。

第四次循环,1进入Find函数后,parent[1]=5,所以继续进行小循环,f=parent[1]=5,parent[5]=0,退出循环,n=5,8进入循环后,立即退出,m=8,parent[5]=8,边B--G加入生成树。

此时的parent为[1,5,0,0,7,8,0,0,0].

第五次循环,3进入Find函数,parent[3]=0,立即返回,n=3,7进入Find函数,立即返回,m=7,parent[3]=7,边D--H加入生成树。

此时的parent为[1,5,0,7,7,8,0,0,0].

第六次循环,1进入Find函数,parent[1]=5,继续,f=parent[1]=5,parent[5]=8,f=parent[5]=8,parent[8]=0,返回,n=8,6进入Find函数,parent[6]=0,返回,m=6,parent[8]=6,边B--G加入生成树。

此时的parent为[1,5,0,7,7,8,0,0,6].

第七次循环,5进入Find函数,parent[5]=8,f=parent[5]=8,parent[8]=6,f=parent[8]=6,parent[6]=0,返回,n=6,6进入函数,parent[6]=0,返回,m=6,此时m=n,成环,由图可以看出的确成环,具体原理在下方。

第八次循环,1进入Find函数,parent[1]=5,f=parengt[1]=5,parent[5]=8,f=parent[5]=8,parent[8]=6,f=parent[6]=0,返回,n=6,2进入函数,parent[2]=0,返回,m=2,不成环,parent[6]=2,边B--C加入生成树。

此时的parent为[1,5,0,7,7,8,2,0,6].

第九次循环,6进入Find函数,parent[6]=2,f=parent[6]=2,parent[2]=0,返回,n=2,7进入循环,parent[7]=0,返回,m=7,不成环,parent[2]=7,边G--H加入生成树。

由于9个顶点只需要八条边就能完成生成树,所以到此,生成树其实已经完成。我们可以试一试,此时,继续进入parent函数会发生什么。

第十次循环,3进入parent函数,parent[3]=7,f=parent[3]=7,parent[7]=0,返回,n=7,4进入循环,parent[4]=7,parent[7]=0,m=7,我们发现,当parent数组中只有一个值为0的情况下,任意两个数传进Find函数,一定都是等于那个值为0的下标,后序所有的循环无意义。

0x04.原理详解

parent数组和Find函数究竟如何理解?

parent数组实际上是并查集这种数据结构的一种表达,关于并查集可以百度了解。

在这个算法的不断加入生成树中,其实形成了许多的小树,而整个算法的目的就是将这些小树连接成一棵树,Find函数的目的就是找到每棵小树的根结点,也就是说返回的n和m的值其实是两棵小树的根节点,如果相等,那么就有两棵小树的根结点相同,既然是两棵小树,根节点相同了,肯定就是连通了,如果不相等,就连通两棵小树,连通的代码是parent[n]=m,这端代码的实际含义就是连通了两棵小树,并以新加入的结点作为的根结点,其中Find函数的那个小循环其实就是寻找一颗小树的根结点,如果和其它没有连通,那么这条边单独做为一棵小树,并以一头为根,f=parent[f],这段代码其实就是不断向上去找的含义,去找到根结点。

原理如上了,那么我们怎么去具体理解这些代码呢?

对于parent数组,我们可以看看什么时候往里加了元素,加的元素的下标和值都是什么意义,不难发现,parent数组一直只是往里添加并查看元素,并没有改变过里面元素的内容,添加元素的关键代码就是parent[n]=m,也就是说在n的位置上添加数值m,我们从刚开始的时候去看,刚开始parent里面都为0,进入Find函数都是直接返回,如第一条边,4,7,直接返回后,有parent[4]=7,这个就是说,下标为4的顶点的根设置为7,我们再来看一下第二个循环,0,1,都是立即返回,parent[0]=1,说明下标为0的结点的根为1,第三次,0,5,进入Find函数后,返回n=1,也就是说定的顶点A的根是1,也就是B,m=5,然后有parent[1]=5,说明顶点B的根结点变成了5,就这样一次理解,我们就可以知道parent数组里面的内容含义是什么。

对于Find函数,我们可以看一下参数和返回分别是什么样的情况,参数是f,也就是边集数组里的边的一个端点,返回条件是,parent数组中的f位置的值为0,其实也就是找到根结点了,f=parent[f],就是不断的向上去找根结点,可以理解为一个顶点的根节点找到后,再继续找这个根结点的根节点,最后找到了这棵树的根结点。

0x05.克鲁斯卡尔算法感悟

这个算法巧妙的利用了一个已经排序的边集数组,然后从头开始去把里面的边加入生成树,应为是有序的,所以从一开始,就已经满足了最小的这个条件,接下来只要判断这些边能不能生成树,也就是由没有闭合就行了,代码简便不少,非常精炼。

0x06.普里姆算法和克鲁斯卡尔算法比较

普里姆主要针对顶点展开,而克鲁斯卡尔主要针对边,各有优势,前者适合稠密图,后者适合稀疏图。

 

 

 

本章结束。

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!