必威体育Betway必威体育官网
当前位置:首页 > IT技术

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

时间:2019-10-07 23:45:40来源:IT技术作者:seo实验室小编阅读:80次「手机版」
 

克鲁斯卡尔算法

关于点击这里->普里姆算法

克鲁斯卡尔算法百度到的解释是:克鲁斯卡尔算法是一种用来寻找最小生成树的算法。在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。这同样是一个比较难以理解的算法,这一次我又是看了别人的博客才理解了一点。

1.将图的所有连接线去掉,只剩顶点

2.从图的边集数组中找到权值最小的边,将边的两个顶点连接起来

3.继续寻找权值最小的边,将两个顶点之间连接起来,如果选择的边使得最小生成树出现了环路,则放弃该边,选择权值次小的边

4.直到所有的顶点都被连接在一起并且没有环路,最小生成树就生成了。

先来看核心代码,然后在解释代码:

int Find(int *parent, int f)
{
//从下标f开始寻找parent数组中元素为0的下标
	while (parent[f]>0)
	{
		f = parent[f];
	}
	return f;
}



//最小生成树,克鲁斯卡尔算法
void Kruskal(MGraph *G)
{
	
	int parent[MAXVERTEX];//存放最小生成树的顶点
	for (int i = 0; i < G->numvertex; i++)
	{
		parent[i] = 0;  //初始化全部为0,
	}
	int m, n;
	for (int i = 0; i < G->numedges; i++)
	{
		n = Find(parent, G->edges[i].begin); //从该边的起点开始寻找parent数组中为0的元素的下标,
		m = Find(parent, G->edges[i].end);//从该边的终点点开始寻找parent数组中为0的元素的下标,
		if (n != m)//如果m=n说明如果连接这条边,则从该边的起点和终点最终都会到达同一顶点,即有环
		{
			parent[n] = m;
			printf("(%d,%d) %d\t", G->edges[i].begin, G->edges[i].end, G->edges[i].wight);//打印边和权值
		}
	}
}

这里为了方便,我在生成图的时候利用的是边集数组,并且已经是按权值从小到大排好序的。

可以看到我们定义了一个parent数组,这个数组是干嘛的呢?这个数组就是用来判断边与边是否形成环路的,现在你可能还不太理解。现在你可以将最小生成树的整个过程想象成一个修路的过程,图中的每一个顶点作为一个村庄,现在我们要把村庄连起来,修总距离最短的路,但是不能使村庄之间出现环路,就是比如我要在A和B之间修路,在两个村庄修路连通之前我们先检查一下从A出发和从B出发能否到达同一个村庄,能则说明有环,不能修,不能则说明没环,可以修。我们把parent想象成一幅地图,每修一条路我们就标记一下。

图结构

边集数组

我们来看一下对于上图的这种图,克鲁斯卡尔算法的操作。

第一步初始化parent数组全部为0,意味着所有顶点都没有连接,也就是现在村庄与村庄之间是没有路的状态

for (int i = 0; i < G->numvertex; i++)
	{
		parent[i] = 0;  //初始化全部为0,
	}

parent

0

0

0

0

0

0

0

0

0

第二步循环寻找边集数组中权值小的边(由于我们事前已经排序过,所以只要从边集数组的第0个元素往下判断是否有环即可)

.

0.(4,7)这条边判断是否有环,代码如下:

int Find(int *parent, int f)
{
//从下标f开始寻找parent数组中元素为0的下标
	while (parent[f]>0)
	{
		f = parent[f];
	}
	return f;
}
n = Find(parent, G->edges[i].begin); //从该边的起点开始寻找parent数组中为0的元素的下标,
m = Find(parent, G->edges[i].end);//从该边的终点点开始寻找parent数组中为0的元素的下标,
if (n != m)//如果m=n说明如果连接这条边,则从该边的起点和终点最终都会到达同一顶点,即有环
{
	parent[n] = m;
	printf("(%d,%d) %d\t", G->edges[i].begin, G->edges[i].end, G->edges[i].wight);//打印边和权值
}

parent

0

0

0

0

0

0

0

0

0

我们从4出发,现在来看parent数组,parent[4]=0,(注:parent[i]=0,意味着该连通路继续往下没有路了)意思是到4就停了,此时得到n=4;

我们从7出发,parent[7]=0,到7就停了,也没有走出去,此时得到m=7;所以从4、7出发不会走到同一个村庄,修路,改变地图parent数组的值,parent[4]=7,意味着从村庄4出发能走到7,修了一条路。

parent

0

0

0

0

7

0

0

0

0

顶点4连到顶点7

1.边(2,8)

parent

0

0

0

0

7

0

0

0

0

parent[2]=0,parent[8]=0,参照地图parent,从村庄2和8出发没有走到同一个村庄,所以放心大胆修条路,n=2,m=8;将2与8连接,改变parent数组,parent[2]=8,现在从2出发能走到村庄8了。

parent

0

0

8

0

7

0

0

0

0

2.边(0,1)

parent

0

0

8

0

7

0

0

0

0

parent[0]=0,parent[1]=0;与上面情况一样,放心大胆修吧,n=0,m=1,更新地图parent[0]=1,意味着从0出发能走到1.

parent

1

0

8

0

7

0

0

0

0

3.边(0,5)

parent

1

0

8

0

7

0

0

0

0

我们从1出发,parent[0]=1,说明此时0村庄已经有一条路通往外界了,它和1是连接的,那么按照parent的指引,我们来到了村庄1发现没路了parent[1]=0,最后到达村庄1,

我们从5出发,parent[5]=0,地图指示没有路往下走了,没有走到同一个村庄,所以可以在村庄0和5之间修路了,更新一下地图parent[1]=5。这里需要解释一下,为什么是在村庄0和5之间修路呢,因为我们要检查的就是能不能在0和5之间修路,既然结论是可以,那么就在0和5之间修条路就行了,那为什么标记地图的时候标记的是parent[1]=5呢?因为我们的地图是按照修路的顺序进行更新的,上次0和1已经修通路了按照指示我们知道0可以到1了,这次又修了5和他们连通,我们就接下来继续标记,即从连通路中=0的那个下标开始标记下一个修路的村庄。意味着0可以到1可以到5

parent

1

5

8

0

7

0

0

0

0

4.边(1,8)

parent

1

5

8

0

7

0

0

0

0

我们从1出发,parent[1]=5,我们走到村庄5,parent[5]=0,最后到达村庄5,,

我们从8出发,parent[8]=0,最后就在村庄8,说明我们没有到同一个村庄,修路修路!!!更新地图parent[5]=8

parent

1

5

8

0

7

8

0

0

0

将1与8连起来

5.边(3,7)

parent

1

5

8

0

7

8

0

0

0

parent[3]=0,parent[7]=0,说明从3和7出发也不会走到同一个村庄,修路,更新地图

parent

1

5

8

7

7

8

0

0

0

6.边(1,6)

parent

1

5

8

7

7

8

0

0

0

我们从1出发,parent[1]=5,来到村庄5,parent[5]=8;来到村庄8,parent[8]=0,我们最后到达村庄8,

我们从6出发,parent[6]=0,最后到达6,没有到的同一村庄,在1,6之间修路,更新地图,parent[8]=6;

parent

1

5

8

7

7

8

0

0

6

7.边(5,6)

parent

1

5

8

7

7

8

0

0

6

我们从5出发,parent[5]=8;来到村庄8,parent[8]=6,来到村庄6,parent[6]=0;所以最好到达村庄6;

我们从6出发,parent[6]=0,最后也是止于村庄6,连通5和6村庄就会出现环路,所以5和6不能修路

8.边(1,2)

parent

1

5

8

7

7

8

0

0

6

我们从1出发,parent[1]=5,来到5,parent[5]=8,来到8,parent[8]=6,来到6,parent[6]=0,到达6终止

我们从2出发,paerent[2]=8,来到8,parent[8]=6,来到6,parent[6]=0,到达6终止,修路会出现环路,所以不修

9.边(6,7)

parent

1

5

8

7

7

8

0

0

6

我们从6出发,parent[6]=0,止于6;

我们从7出发,parent[7]=0,止于7;修路,更新parent

parent

1

5

8

7

7

8

7

0

6

继续执行检查每条边发现在没有符合情况的了,那么克鲁斯卡尔算法生成的最小生成树就生成完了

完整代码:

#include<stdio.h>

#define MAXEDGE 100
#define MAXVERTEX 100
typedef struct Edge
{
	int begin;//边的起点
	int end;  //边的终点
	int wight;//边的权值
}Edge;

typedef struct Graph
{
	char vertex[MAXVERTEX];//顶点
	Edge edges[MAXEDGE];//边
	int numvertex,numedges;//顶点和边的个数
}MGraph;

void CreateGraph(MGraph* G)
{
	printf("请输入顶点和边的个数:\n");
	scanf("%d%d", &G->numvertex, &G->numedges);
	printf("请输入顶点:\n");
	getchar();//利用该函数除去上一系我们在输入结束时按得回车符
	for (int i = 0; i < G->numvertex; i++)
	{
		scanf("%c", &G->vertex[i]);
	}
	printf("按权值从小到大输入边(vi,vj)对应的起点和终点的下标,begin,end以及权值wight:\n");
	for (int k = 0; k < G->numedges; k++)
	{
		Edge e;
		scanf("%d%d%d", &e.begin, &e.end, &e.wight);
		G->edges[k] = e;
	}
}

int Find(int *parent, int f)
{
	while (parent[f]>0)
	{
		f = parent[f];
	}
	return f;
}

//最小生成树,克鲁斯卡尔算法
void Kruskal(MGraph *G)
{
	
	int parent[MAXVERTEX];//存放最小生成树的顶点
	for (int i = 0; i < G->numvertex; i++)
	{
		parent[i] = 0;
	}
	int m, n;
	for (int i = 0; i < G->numedges; i++)
	{
		n = Find(parent, G->edges[i].begin);
		m = Find(parent, G->edges[i].end);
		if (n != m)//m=n说明有环
		{
			parent[n] = m;
			printf("(%d,%d) %d\t", G->edges[i].begin, G->edges[i].end, G->edges[i].wight);//打印边和权值
		}
	}
}

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

对于上面的那张图最后输出 (4,7)7,(2,8)8,(0,1)10,(0,5)11,(1,8)12,(3,7)16,(1,6)16,(6,7)19

最后,自己的理解也比较浅显,记录一下,加深印象,希望日后能有更深的理解。

相关阅读

你是如何坚持读完《算法导论》这本书的?

你是如何坚持读完《算法导论》这本书的? 《算法导论》不够猛,答者顺便补充 “你是如何坚持读完《计算机编程的艺术》这本书的?”

AI图像识别:人类看的是形状,算法看的是纹理

人类会关注图中对象的形状,深度学习计算机系统所用的算法不一样,它会研究对象的纹理。图片中的动物轮廓是猫,但是猫披着大象皮肤纹理

基于深度学习的人脸识别算法

基于深度学习的人脸识别算法简介Contrastive LossTriplet LossCenter LossA-Softmax Loss参考文献:简介 我们经常能从电影中看到各

AIC(最小信息化准则)

AIC信息准则(即Akaike information criterion),是用来衡量统计模型拟合优良性的一个标准,是是由日本统计学家赤池弘次创立和发展的,因

【数据结构与算法】初入数据结构的哈希表(Hash Table)

初入数据结构的哈希表(Hash Table) 这次我们来总结一下关于哈希表的知识,首先我们要了解什么是哈希表,哈希函数的构造思路有哪些

分享到:

栏目导航

推荐阅读

热门阅读