最小生成树 普里姆算法

构造成连通网的最小代价生成树(MinimumCostSpanningTree)称为最小生成树(简称MST)。

找连通网的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法。

普里姆(Prim)算法

对于图中的网来看,找出了它所有的生成树,其中T3和T5的各边权值之和最小,所以T3和T5都是最小生成树,可见最小生成树不一定为1。

最小生成树 普里姆算法_第1张图片

普里姆算法思想:

假设N= (P;{E}) 是连通网,TE是N上最小生成树中边的集合。算法从U={uo}(uo∈V), TE={}开始。重复执行下述操作:在所有u∈U,v∈V←U的边(u,v) ∈E中找-一条代价最小的边(uo,vo) 并入集合TE,同时vo并入U,直至U=V为止。此时TE中必有n-1条边,则T= (V,{TE})为N的最小生成树。

  • G=(V,E)是具有n个顶点的连通图,设U是最小生成树中顶点的集合,TE是最小生成树中边的集合;
  • 初始,U={u1},TE={ }
  • 重复执行:在所有u∈U,v∈V-U的边(u,v)中寻找代价最小的边(u’,v’),并纳入集合TE中;同时将v’纳入集合U中;
  • 直至U=V为止。
  • 集合TE中必有n-1条边
  • 普利姆算法的时间复杂度为O(n2),与网中的边数无关,因此适用于求边稠密的网的最小生成树。

最小生成树 普里姆算法_第2张图片

void MiniSpanTree Prim(MGraph G)
{
	int min, i, j, k;
	int adjvex[MAXVEX]; /* 保存相关顶点下标*/
	int lowcost[MAXVEX]; /* 保存相关顶点间边的权值 */
	lowcost[0] = 0;  /*初始化第一个权值为0, 即v。加入生成树* /
					/* lowcost的值为0,在这里就是此下标的顶点已经加入生成树*/
	adjvex[0] = 0; /* 初始化第一一个顶点下标为0 */
	for (i = 1; i < G.numVertexes; i++) /*循环除下标为0外的全部顶点*/
	{
		lowcost[i] = G.arc[0][i];/*将Vo顶点与之有边的权值存入数组*/
		adjvex[i] = 0;			/*初始化都为v0的下标*/
	}
	for (i = 1; 1 < G.numVertexes; i++)
	{
		min = INFINITY; /*初始化最小权值为∞,*/
		/*通常设置为不可能的大数字如32767、 65535等*/
		j = 1; k = 0;
		while (j < G.numVertexes)	/*循环全部顶点 */
		{
			if (lowcost[j]!= 0 && lowcost[j] < min)      //生成树的顶点不参与最小权值的查找
			{/*如果权值不为0且权值小于min */
				min = lowcost[j];	/* 则让当前权值成为最小值*/
				k = j;		/*将当前最小值的下标存入k */
			}
			j++;
		}
		printf("%d,%d", adjvex[k], k);/*打印当前顶点边中权值最小边*/
		lowcost[k] = 0;/*将当前顶点的权值设置为0, 表示此顶点已经完成任务*/
		for (j = 1; j < G.numVertexes; j++)/*循环所有顶点 */
		{
			if (lowcost[j] != 0 && G.arc[k][j] < lowcost[j])
			{
				/*若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值*/
				lowcost[j] = G.arc[k][j];/*将较小权值存入lowcost*/
				adjvex[j] = k;			/*将下标为k的顶点存入adjvex */
			}
		}
	}
}

克鲁斯卡尔算法

同样的 思路,我们也可以直接就以边为 目标去构建,因为权值是在边上,直接去 找最小权值的边来构建生成树也是很自然的想法,只不过构建时要考虑是否会形成环路而已。此时我们就用到了图的存储结构中的边集数组结构。

我们将图的邻接矩阵通过程序转化为右图的边集数组,并且对它们按权值从小到大排序。

最小生成树 普里姆算法_第3张图片

使用贪心准则,从剩下的边中选择具有最小权值且不会产生环路的边加入到生成树的边集中。其操作为:

  • 确定权值最小的边
  • 判断一条边所关联的两个顶点是否在一个联通分量中;
  • 如果不是则合并两个顶点所属的连通分量。

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

/*对边集数组Edge结构的定义*/
typedef struct
{
	int begin;
	int end;
	int weight;
}Edge;

//Kruskal算法生成最小生成树
void MiniSpanTreeKruskal(MGraph G) /*生成最小生成树*/
{
	int i, n, m;
	Edge edges[MAXEDGE]; /*定义边集数组*/
	int parent[MAXVEX]; /*定义一数组用 来判断边与边是否形成环路*/
 /*此处省略将邻接矩阵G转化为边集数组edges并按权由小到大排序的代码*/
	for (i = 0; i < G.numVertexes; i++)
		parent[i] = 0; /* 初始化数组值为0 */
	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", edges[i].begin, edges[i].end, edges[i].weight);
		}	
	}
}

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

 

你可能感兴趣的:(数据结构)