最小生成树

最小生成树

解释:一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。构造连通网的最小代价生成树 称为最小生成树。

MST性质:假设N={V,{E}}是一个连通图,U是顶点集V的一个非空子集,若(u,v)是一条具有最小权值(代价)的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。

经典算法:普里姆算法和克鲁斯卡尔算法。

普里姆算法:

算法的实现定义
假设N=(V,{E})是连通网,TE是N上最小生成树中边的集合。算法从U={u0}(任意顶点)(u0∈V),TE={}开始。重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找到一条代价最小的边(u0,v0)并入集合TE,同时v0并入U(表示已被纳入到生成树),直至U=V(包含所有顶点)为止。此时TE必有n-1条边,则T=(V,{TE})为N的最小生成树。(算法完整代码见P247)

分步描述:

  • 定义代码
int min,i,j,k;
int adjvex[MAXVEX];			/*保存相关顶点下标*/
int lowcost[MAXVEX];		/*保存相关顶点间边的权值*/
lowcost[0]=0;				/*代表v0已经纳入生成树*/

注意:lowcost[i]=0代表此下标的顶点被纳入最小生成树。

  • 循环除下标为0外的全部顶点
for(i=1;i<G.numVertexes;i++)
{
	lowcost[i]=G.arc[0][i];	/*将v0顶点与之有边的权值存入数组*/
	adjvex[i]=0;			/*初始化为v0的下标*/
}
  • 构造最小生成树
for(i=1;i<G.numVertexes;i++)
{
	min=65535;			/*初始化最小权值*/
	j=1,k=0;						
	while(j<G.numVertexes)			/*循环全部顶点找出某一项点所有路径上的最小权值赋给min,并将下标存入k*/
	{
		if(lowcost[j]!=0&&lowcost[j]<min)	/*lowcost[j]==0表示已经是生成树的顶点不参与最小权值的查找*/
		{
			min=lowcost[j];
			k=j;
		}
		j++;
	}
	printf("(%d,%d)",adjvex[k],k);	/*打印当前顶点边中权值最小边*/
	lowcost[k]=0;					/*表示Vk点已纳入*/
	for(j=1;j<G.numVertexes;j++)			/*为了查找U->V-U的边中最小权值*/
	{
		if(lowcost[j]!=0&&G.arc[k][j]<lowcost[j])		/*若下标为k顶点各边权值小于此前这些对应的权值*/
		{
			lowcost[j]=G.arc[k][j];					/*在赋制之前一些相对较大者被除去*/
			adjvex[j]=k;					/*记录替换值的起点下标*/
		}
	}
}

以下图为例简述算法运行过程:

在这里插入图片描述
(1)读取将邻接矩阵的第一行数据(即关于V0的所有边的权值),并赋给lowcost数组,所以此时数组元素为{0,10,65535,65535,65535,11,65535,65535,65535},adjvex数组均为0。

(2)寻找到最小权值lowcost[j],用min存储,同时用k存储其下标。结果为k=1,min=10。

(3)打印结果为(0,1),表示v0至v1边为最小生成树的第一条边。

(4)lowcost[1]=0,代表v1纳入,lowcost数组{0,0,65535,65535,65535,11,65535,65535,65535}。

(5)查找邻接矩阵的第v1行的各个权值,与lowcost的对应值进行比较,若更小则修改lowcost值,并将k值存入adjvex数组。因第v1行有18,16,12均比65535小,则进行替换。结果为:lowcost数组元素为{0,0,18,65535,65535,11,16,65535,12},adjvex数组值为{0,0,1,0,0,0,1,0,1}。

(6)以此类推。当U=V时,即lowcost数组元素均为0时,可找到最小生成树。

克鲁斯卡尔算法:

算法的实现定义
假设N=(V,{E})是连通图,则令最小生成树的初始状态为只有n个顶点而无边(离散的点)的非连通图T={V,{}},图中每个顶点自成一个连通分量(一个集合)。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条代价最小的边,依此类推,直至T中所有顶点都在同一连通分量上为止。

分步描述:

  • 定义代码
int i,n,m;
Edge edges[MAXEDGE];		/*定义边集数组*/
int parent[MAXVEX];			/*用来判断边与边是否形成环路*/
/*省略将邻接矩阵G转化为边集数组edges并按权值从小到大排序的代码*/
  • 初始化数组
for(i=0;i<G.numVertexes;i++)
	parent[i]=0;			/*可表示孤立的顶点,不与其他点相连通*/

注意:parent数组:下标为起点,值为结尾顶点,可表示两点间是相通的。(或是通过将值换为下标的方法找到连通的边集合)

  • 构成最小生成树
for(i=0;i<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",edges[i].begin,edges[i].end,edges[i].weight);
	}
}

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

以下图为例简述算法运行过程:
在这里插入图片描述
(1)初始化parent数组。

(2)对边集数组进行循环遍历,从i=0开始。

(3)调用函数Find,传入的参数是数组parent和当前权值最小边(v4,v7)edges[0].begin=4,因parent值均为0,则传出值为n=4。同理m=7。因n!=m,parent[4]=7,则parent数组值为{0,0,0,0,7,0,0,0,0,},并打印得到“(4,7)7”。表示v4,v7已纳入到最小生成树中。以此类推。

(4)i=6时,有两个连通的边集合A与B中纳入到最小生成树中。A={v0,v1,v2,v5,v8,v6},B={v3,v4,v7}。

(5)到i=7时,n=m=6,不再打印,即边(v5,v6)使连通的边集合A产生了回路。i=8时,同理边(v1,v2)形成环路。

(6)当i=9时,边(v6,v7),可得到n=6,m=7,因此parent[6]=7,打印“(6,7)19”。parent数组值为{1,5,8,7,7,8,7,0,6}。此时U=V。

(7)此后均构成环路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值