C语言-最小生成树(Kruskal算法)

news/2024/7/15 19:34:04 标签: 数据结构, 算法, c语言, 树结构, 图论
  1. 创建边集图(CreateEdgeGraph)
  2. 打印图(print)
  3. 排序函数(sort)
  4. 顶点下标查找函数(LocateVex)
  5. 查找双亲函数(FindRoot)
  6. 克鲁斯卡尔算法(MiniSpanTree_Kruskal)

克鲁斯卡尔算法

  • 简单的来说就是:每次选取最短边,但不能构成回路。

克鲁斯卡尔算法的关键

1. 用哪一种方式存储图才合适?

  • 如果用邻接矩阵和邻接表,每次寻找最短边都要搜索所有边,故邻接矩阵和邻接表均不合适!
  • 改进图的存储:边集数组。
  • EdgeGraph中包含了两个数组和顶点数、边数。两个数组分别是顶点数组Vertex[ ]和边集数组edge[ ],其中边集数组edge是一个结构体数组,每一个单元包含起点、终点、权值。
typedef struct
{
	VertexType begin;//起点
	VertexType end;//终点
	int weight;
}Edge;//边集数组edge[]的单元 

typedef struct
{
	VertexType Vertex[VertexMax];//顶点数组 
	Edge edge[VertexMax];//边集数组 
	int vexnum;//顶点数 
	int edgenum;//边数 
}EdgeGraph;

2. 如何选取最短边?

  • 排序。排序是克鲁斯卡尔算法的关键,是整个算法时间花费最多的部分,故排序算法决定了克鲁斯卡尔算法的时间复杂度。若采用插入排序,时间复杂度为O(e2) ,若采用堆排序或者快速排序,那么时间复杂度为O(elog2e) [注:e为边数]
  • 此处用的是简单选择排序:
void sort(EdgeGraph *E)
{
	int i,j;
	Edge temp;
	
	for(i=0;i<E->edgenum-1;i++)
	{
		for(j=i+1;j<E->edgenum;j++)
		{
			if(E->edge[i].weight>E->edge[j].weight)
			{
				temp=E->edge[i];
				E->edge[i]=E->edge[j];
				E->edge[j]=temp;
			}
		}
	}
}
  • 排序结果:

3. 如何确定当前所选最短边是否会构成回路?

  • 考察两个顶点之间是否会构成回路,只需要看两个顶点所属的树是否有相同的根节点。
int FindRoot(int t,int parent[])//t接收到是结点在Vertex数组中的下标 
{
	while(parent[t]>-1)//parent=-1表示没有双亲,即没有根节点 
	{
		t=parent[t];//逐代查找根节点 
	}
	
	return t;//将找到的根节点返回,若没有根节点返回自身 
}

4. 克鲁斯卡尔算法代码:

void MiniSpanTree_Kruskal(EdgeGraph *E)
{
	int i;
	int num;//生成边计数器,当num=顶点数-1 就代表最小生成树生成完毕 
	int root1,root2; 
	int LocVex1,LocVex2; 
	int parent[VertexMax];//用于查找顶点的双亲,判断两个顶点间是否有共同的双亲,来判断生成树是否会成环 
	
	//1.按权值从小到大排序 
	sort(E);
	print(E);
	//2.初始化parent数组 
	for(i=0;i<E->vexnum;i++)
	{
		parent[i]=-1;
	}
	
	printf("\n 最小生成树(Kruskal):\n\n");
	//3.
	for(num=0,i=0;i<E->edgenum;i++)
	{
		LocVex1=LocateVex(E,E->edge[i].begin);
		LocVex2=LocateVex(E,E->edge[i].end);
		root1=FindRoot(LocVex1,parent);
		root2=FindRoot(LocVex2,parent);
	
		
		if(root1!=root2)//若不会成环,则在最小生成树中构建此边 
		{
			printf("\t\t%c-%c w=%d\n",E->edge[i].begin,E->edge[i].end,E->edge[i].weight);//输出此边 
			parent[root2]=root1;//合并生成树
			num++;
			
			if(num==E->vexnum-1)//若num=顶点数-1,代表树生成完毕,提前返回 
			{
				return;
			}
		} 
	}
	
}
  • 需要注意的是我这里采用的顶点元素是字符型,所以在找祖先(FindRoot)的时候需要将顶点元素对应Vertex数组中的下标传入,故需要用LocateVex函数获取下标[见上代码第22-25行]
  • 此代码还加入了生成边计数器num,统计当前最小生成树的边数,当num达到顶点数-1(n-1)的时候,可以提前返回结束,省去后面可能会有多余的步骤。[见上代码第34-37行]

完整源代码

#include <stdio.h>
#include <stdlib.h>
#define VertexMax 20 //最大顶点数为20

typedef char VertexType; 

typedef struct
{
	VertexType begin;
	VertexType end;
	int weight;
}Edge;//边集数组edge[]的单元 

typedef struct
{
	VertexType Vertex[VertexMax];//顶点数组 
	Edge edge[VertexMax];//边集数组 
	int vexnum;//顶点数 
	int edgenum;//边数 
}EdgeGraph;

void CreateEdgeGraph(EdgeGraph *E)
{
	int i;
	
	printf("请输入顶点数和边数:\n");
	printf("顶点数 n="); 
	scanf("%d",&E->vexnum);
	printf("边  数 e="); 
	scanf("%d",&E->edgenum);
	printf("\n"); 
	//printf("\n"); 
	
	printf("输入顶点(无需空格隔开):"); 
	scanf("%s",E->Vertex);
	printf("\n\n");
	
	printf("输入边信息和权值(如:AB,15):\n");
	for(i=0;i<E->edgenum;i++)
	{
		printf("请输入第%d边的信息:",i+1);
		scanf(" %c%c,%d",&E->edge[i].begin,&E->edge[i].end,&E->edge[i].weight);
	}	
}

void print(EdgeGraph *E)
{
	int i;
	
	printf("\n-----------------------------------\n"); 
	printf(" 顶点数组Vertex:");
	for(i=0;i<E->vexnum;i++)
	{
		printf("%c ",E->Vertex[i]);
	}
	printf("\n\n");
	
	printf(" 边集数组edge:\n\n");
	printf("\t\tBegin	End	Weight\n");
	for(i=0;i<E->edgenum;i++)
	{
		printf("\tedge[%d]	%c	%c	%d\n",i,E->edge[i].begin,E->edge[i].end,E->edge[i].weight);
	}
	printf("\n-----------------------------------\n");
}

void sort(EdgeGraph *E)
{
	int i,j;
	Edge temp;
	
	for(i=0;i<E->edgenum-1;i++)
	{
		for(j=i+1;j<E->edgenum;j++)
		{
			if(E->edge[i].weight>E->edge[j].weight)
			{
				temp=E->edge[i];
				E->edge[i]=E->edge[j];
				E->edge[j]=temp;
			}
		}
	}
}

int LocateVex(EdgeGraph *E,VertexType v)//查找元素v在一维数组 Vertex[] 中的下标,并返回下标 
{
	int i;
	
	for(i=0;i<E->vexnum;i++)
	{
		if(v==E->Vertex[i])
		{
			return i; 
		} 
	 } 
	 
	 printf("No Such Vertex!\n");
	 return -1;
}

int FindRoot(int t,int parent[])//t接收到是结点在Vertex数组中的下标 
{
	while(parent[t]>-1)//parent=-1表示没有双亲,即没有根节点 
	{
		t=parent[t];//逐代查找根节点 
	}
	
	return t;//将找到的根节点返回,若没有根节点返回自身 
}

void MiniSpanTree_Kruskal(EdgeGraph *E)
{
	int i;
	int num;//生成边计数器,当num=顶点数-1 就代表最小生成树生成完毕 
	int root1,root2; 
	int LocVex1,LocVex2; 
	int parent[VertexMax];//用于查找顶点的双亲,判断两个顶点间是否有共同的双亲,来判断生成树是否会成环 
	
	//1.按权值从小到大排序 
	sort(E);
	print(E);
	//2.初始化parent数组 
	for(i=0;i<E->vexnum;i++)
	{
		parent[i]=-1;
	}
	
	printf("\n 最小生成树(Kruskal):\n\n");
	//3.
	for(num=0,i=0;i<E->edgenum;i++)
	{
		LocVex1=LocateVex(E,E->edge[i].begin);
		LocVex2=LocateVex(E,E->edge[i].end);
		root1=FindRoot(LocVex1,parent);
		root2=FindRoot(LocVex2,parent);
	
		
		if(root1!=root2)//若不会成环,则在最小生成树中构建此边 
		{
			printf("\t\t%c-%c w=%d\n",E->edge[i].begin,E->edge[i].end,E->edge[i].weight);//输出此边 
			parent[root2]=root1;//合并生成树
			num++;
			
			if(num==E->vexnum-1)//若num=顶点数-1,代表树生成完毕,提前返回 
			{
				return;
			}
		} 
	}
	
}

int main() 
{
	EdgeGraph E;
	
	CreateEdgeGraph(&E);
	MiniSpanTree_Kruskal(&E);
	
	return 0;
	
}

执行结果

克鲁斯卡尔算法的时间复杂度

  • 上文已经提到,克鲁斯卡尔算法主要时间耗费在排序算法中,整个算法的时间复杂度取决于排序算法,若采用插入排序,时间复杂度为O(e2) ,若采用堆排序或者快速排序,那么时间复杂度为O(elog2e),此处的e为边,那么也就是说,克鲁斯卡尔算法的时间复杂度与边数有关,故适用于稀疏图

参考:

1.部分文图来自 懒猫老师


http://www.niftyadmin.cn/n/1712755.html

相关文章

Unity动画篇:Mecanim动画系统重点整合(获取当前正在播放的动画片段)

1.animator.GetCurrentAnimatorStateInfo(0); //参数是表示层级&#xff0c;返回的当前层级正在播放的动画的状态机&#xff08;Base为0&#xff0c;New为1&#xff0c;以此类推&#xff09; (如果要获取不受缩放影响的原始剪辑&#xff0c;可以用runtimeAnimatorController.…

基准线_房产税呼声再起,专家“免征面积”方案获赞,超过基准线交多少?

到目前为止&#xff0c;房子还是我国居民购买最贵的单品&#xff0c;这里指的是普通居民&#xff0c;有的富豪购买的千万豪车不算在其中。1998年我国开始了商品房“时代”&#xff0c;从此国内居民就过上了买房子生活的日子&#xff0c;而且整个房地产行业也进入了一个快速的发…

Win10问题篇:解决鼠标玩游戏单击(左击/右击)失灵问题。

鼠标失灵原因不外乎有三个。 1.鼠标本身问题。 2.鼠标驱动问题。 3.鼠标线材弯曲问题。 解决方法&#xff1a; 1.下载鼠标测试软件。http://dl.pconline.com.cn/download/413815.html测试是否是鼠标本身问题。 2.进入设备管理器&#xff0c;查看鼠标和其他指针设备是否有…

冲压模板自动标注LISP_干货满满!超实用冲压模具资料,加薪必看!

五金模具设计 - 知乎​www.zhihu.com关注五金模具设计圈&#xff0c;一个模具人的圈子&#xff0c;看更多精彩文章及视频教程一般的冲压模具都是由&#xff1a;上下托板、上下垫脚、上下模座&#xff1a;一般用A3、Q235等“软料”做成&#xff0c;起支撑整个模具、方便架模、落…

C语言-最短路径(Dijskra算法)

顶点下标查找函数&#xff08;LocateVex&#xff09;创建有向网&#xff08;CreateDN&#xff09;打印图函数&#xff08;print&#xff09;展示最短路径函数&#xff08;displayPath&#xff09;查找当前最短路径函数&#xff08;FindMinDist&#xff09;迪杰斯特拉算法&#…

Unity网络篇:NetworkServer is not active. Cannot spawn objects without an active server。解决办法

这个问题字面意思很明显了。NetworkServer未激活。在Unity 3D中&#xff0c;如果没有活动服务器&#xff0c;则无法生成对象。我总结了一下&#xff0c;原因有三个。 一&#xff1a;没有完成服务器的创建。&#xff08;一般都不会是这个错误。&#xff09; 这时候我们需要创建…

C语言-最短路径(Floyd算法)

顶点下标查找函数&#xff08;LocateVex&#xff09;创建有向网&#xff08;CreateDN&#xff09;打印图函数&#xff08;print&#xff09;弗洛伊德算法&#xff08;ShortestPath_Floyd&#xff09;展示最短路径&#xff08;DisplayPath&#xff09; 多源点最短路径 多源点意…

eja变送器故障代码al01_日本横河川仪EJA变送器故障原因及解决办法!

我们在使用横河EJA变送器时难免出出现一些故障&#xff0c;有的是外部环境引起的&#xff0c;有的是操作不当&#xff0c;有的是产品质量&#xff0c;对于不是很懂的客户&#xff0c;当产品出现故障时不知怎么办&#xff0c;下面我整理了日本横河变送器公司公布的官方变送器故障…