数据结构之最小生成树

最小生成树: 一个连通图的生成树是一个极小连通子图,它含有图中全部顶点,但只有足以构成一棵树的n-1条边。这种构造连通网的最小代价生成树称为最小生成树,详见数据结构之图(术语、存储结构、遍历)

求连通网的最小生成树有两种经典方法:普里姆(Prime)算法和克鲁斯卡尔(Kruskal)算法。

1、Prime算法

(1)算法描述

假设N=(V,{E})是连通网,TE是N上最小生成树中边的集合。从V中任选一个顶点u0,算法从U={u0}(u0∈V),TE={}开始,重复执行以下步骤:

在所有u∈U、v∈V-U的边(u, v)∈E中找一条代价最小的边(u, vi)并入集合TE,同时将vi并入U,直至U=V为止。

此时TE中必有n-1条边,则T={V,{TE}}即为N的最小生成树。

需要用到两个辅助数组:

lowcost[MAX_VEM]:存储从U到V-U的具有最小代价边上的权——lowcost[i] = Min{w(u, vi)|u∈U,vi∈V-U}

adjvex[MAX_VEM]:存储从U到V-U的具有最小代价边(u,vi)依附在U中的顶点u的下标——adjvex[i] = {u|lowcost[i] }

(2)实例

给定如下一个无向网:

根据上述prime算法描述,求其最小生成树的过程如下:

最后得到如下最小生成树:

(3)、算法代码

//普里姆(Prime)算法
void MiniSpanTree_Prime(Graph *g, VertexType u0)
{
	int min, i, j, k, v0;

	int adjvex[MAX_VEX];	//存储从U到V-U的具有最小代价边(u,vi)依附在U中的顶点u的下标
	                        //adjvex[i] = {u|lowcost[i] = Min{w(u, vi)|u∈U,vi∈V-U}}
	int lowcost[MAX_VEX];	//存储从U到V-U的具有最小代价边上的权——lowcost[i] = Min{w(u, vi)|u∈U,vi∈V-U}

	v0 = k = LocateVex(g, u0);	//定位开始的顶点u0在顶点数组中的下标号
	assert(k != -1);
	//adjvex[k] = k;	//初始化第一个顶点下标为k
	lowcost[k] = 0;	//初始,U={u}

	for (i = 0; i < g->vexNum; i++)
	{
		if (i != k)	//初始化除下标为k的其余全部顶点
		{
			adjvex[i] = k;	//初始化都为顶点u对应的下标k
			lowcost[i] = g->arc[k][i];	//将顶点u与之有关的权值存入数组
		}
	}

	for (i = 0; i < g->vexNum; i++)
	{
		if (i == v0)
		{
			continue;
		}

		min = INFINITY;
		for (j = 0, k = 0; j < g->vexNum; j++)
		{
			if (lowcost[j] != 0 && lowcost[j] < min)   //V-U中的全部顶点
			{
				min = lowcost[j];
				k = j;
			}
		}

		printf("(%d,%d) ", adjvex[k], k);   //打印当前顶点中权值最小的边:
		                                    //g->vexs[adjvex[k]]∈U,g->vexs[k]∈V-U
		//printf("(%c,%c) ", g->vexs[adjvex[k]], g->vexs[k]);
		lowcost[k] = 0; //第k顶点并入U集

		for (j = 0; j < g->vexNum; j++)
		{
			if (lowcost[j] != 0 && g->arc[k][j] < lowcost[j])
			{
				lowcost[j] = g->arc[k][j];	//新顶点并入U后重新选择最小边
				adjvex[j] = k;
			}
		}
	}
	putchar('\n');
}

运行截图:

分析:邻接矩阵实现的普里姆算法的时间复杂度为O(n^2),与网中的边数无关,适用于求边稠密的网的最小生成树。

2、Kruskal算法

(1)算法描述

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

Kruskal算法主要考虑是否会形成环路。在实际的代码编写中,一般采用边集数组这一数据结构:

//边集数组
#define  MAX_EDGE	100	//最大边数
typedef struct
{
	int begin;
	int end;
	EdgeType weight;
}Edge;

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

       

(2)算法代码

//将邻接矩阵结构转化成边集
void Convert(Graph *g, Edge edge[])
{
	int i, j, k = 0;
	for (i = 0; i < g->vexNum; i++)
		for (j = i; j < g->vexNum; j++)	 //无向图的邻接矩阵是对称的
			if (g->arc[i][j] < INFINITY)
			{
				edge[k].begin = i;
				edge[k].end = j;
				edge[k].weight = g->arc[i][j];
				k++;
			}
#if 0
			printf("排序前:\n");
			printf("       \tbeign\tend\tweight\n");
			for(i = 0; i < k; i++)
			{
				printf("edge[%d]\t%d(%c)\t%d(%c)\t%d\n", i,
					edge[i].begin, g->vexs[edge[i].begin],
					edge[i].end, g->vexs[edge[i].end], edge[i].weight);
			}
#endif
   InsertSort(edge, k);
#if 1
   printf("排序后:\n");
   printf("       \tbeign\tend\tweight\n");
   for(i = 0; i < k; i++)
   {
	   printf("edge[%d]\t%d(%c)\t%d(%c)\t%d\n", i,
		   edge[i].begin, g->vexs[edge[i].begin],
		   edge[i].end, g->vexs[edge[i].end], edge[i].weight);
   }
#endif
}

//按权值大小对边集数组edge从小至大排序
void InsertSort(Edge edge[], int k)
{
	int i, j;
	Edge tmp;

	for (i = 1; i < k; i++)
	{
		if (edge[i].weight < edge[i - 1].weight)
		{
			tmp = edge[i];
			for (j = i - 1; edge[j].weight > tmp.weight; j--)
			{
				edge[j + 1] = edge[j];
			}
			edge[j + 1] = tmp;
		}
	}
}

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

//克鲁斯卡尔算法实现
void MiniSpanTree_Kruskal(Graph *g)
{
	int i, n, m;
	Edge edges[MAX_EDGE];    //定义边集数组
	int parent[MAX_EDGE];     //定义一数组用来判断边与边是否形成环

	//将邻接矩阵转化为边集数组edges并按权值由小到大排序
	Convert(g, edges);

	for(i = 0; i < g->vexNum; i++)
	{
		parent[i] = 0;  //初始化数组值为0
	}

	for(i = 0; i < g->arcNum; 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);
			printf("(%c,%c) ", g->vexs[edges[i].begin], g->vexs[edges[i].end]);
		}
	}
	putchar('\n');
}

运行截图:

不考虑邻接矩阵或邻接表转化为边集数组的时间开销,克鲁斯卡尔算法的Find函数由边数e决定,时间复杂度为O(loge),而外面有一个for循环e次,所以克鲁斯卡尔算法的时间复杂度为O(eloge),相对prime算法而言,适合于求边稀疏的网的最小生成树。

参考:这篇文章的url提示不对呀 “*
文章包含被禁用的url,无法保存和发布。”
,没法给出参考链接了

数据结构(C语言版)

完整的测试代码:晚点上链接,资源上传了估计还在审核

数据结构之最小生成树

时间: 2024-08-06 11:36:10

数据结构之最小生成树的相关文章

数据结构:最小生成树--Prim算法

最小生成树:Prim算法 最小生成树 给定一无向带权图,顶点数是n,要使图连通只需n-1条边,若这n-1条边的权值和最小,则称有这n个顶点和n-1条边构成了图的最小生成树(minimum-cost spanning tree). Prim算法 Prim算法是解决最小生成树的常用算法.它采取贪心策略,从指定的顶点开始寻找最小权值的邻接点.图G=<V,E>,初始时S={V0},把与V0相邻接,且边的权值最小的顶点加入到S.不断地把S中的顶点与V-S中顶点的最小权值边加入,直到所有顶点都已加入到S中

数据结构--图--最小生成树(Prim算法)

构造连通网的最小生成树,就是使生成树的边的权值之和最小化.常用的有Prim和Kruskal算法.先看Prim算法:假设N={V,{E}}是连通网,TE是N上最小生成树中边的集合.算法从U={u0}(uo属于V),TE={}开始,重复执行下述操作:在所有u属于U,v属于V-U的边(u,v)属于E中找到代价最小的一条边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止.此时TE中必有n-1条边,T={V,{TE}}为N的最小生成树.为实现此算法,需另设一个辅助数组closedge,以记录从U

白话数据结构之最小生成树

一:基本概念 1:什么是生成树? 对于图G<V,E>,如果其子图G'<V',E'>满足V'=V,且G'是一棵树,那么G'就是图G的一颗生成树.生成树是一棵树,按照树的定义,每个顶点都能访问到任何一个其它顶点.(离散数学中的概念),其中V是顶点,E是边,通俗来讲生成树必须包含原图中的所有节点且是连通的 比如 2:最小生成树 一个无向连通图G=(V,E),最小生成树就是联结所有顶点的边的权值和最小时的子图T,此时T无回路且连接所有的顶点,所以它必须是棵树.就是将原图的n个顶点用 n-1

数据结构-图-最小生成树

最小生成树表示得是连通图的极小连通子图,它包含所有的的顶点,但足以生成n-1条边的数. 下面是我学习的内容和理解. 1.使用普里姆算法构成最小生成树. 先讲一下普里姆算法的思路.普里姆算法思路是这样的: 前提:G={V,E} 这个是我们图的定义这个应该是明白啥意思的. 1.现在定义一个V1表示一个空的新集合 2.首先随机的将V中的一个节点放入到V1中,作为遍历图开始位置 3.其次选取V1中的一个节点和选取V中的一个节点,使边的权值是E中最小的(必须是存在E中的,不然不是两个节点不是连通的,也没用

数据结构例程——最小生成树的普里姆算法

本文是[数据结构基础系列(7):图]中第11课时[最小生成树的普里姆算法]的例程. (程序中graph.h是图存储结构的"算法库"中的头文件,详情请单击链接-) #include <stdio.h> #include <malloc.h> #include "graph.h" void Prim(MGraph g,int v) { int lowcost[MAXV]; //顶点i是否在U中 int min; int closest[MAXV]

数据结构:最小生成树--Kruskal算法

Kruskal算法 Kruskal算法 求解最小生成树的还有一种常见算法是Kruskal算法.它比Prim算法更直观.从直观上看,Kruskal算法的做法是:每次都从剩余边中选取权值最小的,当然,这条边不能使已有的边产生回路. 手动求解会发现Kruskal算法异常简单,以下是一个样例 先对边的权值排个序:1(0,1) 2(2,6) 4(1,3) 6(1,2) 8(3,6) 10(5,6) 12(3,5) 15(4,5) 20(0,1) 首选边1(0,1).2(2,6).4(1,3).6(1,2)

【数据结构】 最小生成树(二)——kruskal算法

上一期说完了什么是最小生成树,这一期咱们来介绍求最小生成树的算法:kruskal算法,适用于稀疏图,也就是同样个数的节点,边越少就越快,到了数据结构与算法这个阶段了,做题靠的就是速度快,时间复杂度小. 网上一搜就知道大家都会先介绍prim算法,而我为什么不介绍prim算法呢?因为小编认为这个算法理解快,也很容易明白,可以先做个铺垫(小编绝不会告诉你小编是因为不会才不说的),kruskal算法核心思想是将一棵棵树林(也可以理解成子树)合并成一棵大树,具体做法如下:将一个连通图中不停寻找最短的边,如

数据结构9——最小生成树

最小生成树:这里面有两个概念:(1):必须为一个树,并且为一棵生成树(树的定义有且仅有一个前驱结点,可以有有多个后驱子节点,并且(n-1)条边都在图中)        (2):必须是最小连通的.(多一条边会使树形成回路,不满足生成树的概念,少一条边,则会使树不连通) 因此,最小生成树必须满足三个条件: (1):必须包含n个结点 (2):必须包含(n-1) 条边 (3):不能构成回路. 最小生成树的两种构造方法:Prim算法,Kruskal算法. 1:Prim算法生成最小生成树 prim 算法的伪

数据结构--画画--最小生成树(Prim算法)

通信网络的最小生成树配置,它是使右侧的生成树值并最小化.经常使用Prim和Kruskal算法.看Prim算法:以防万一N={V,{E}}它是在通信网络,TE它是N设置边的最小生成树.从算法U={u0}(uo属于V).TE={}开始,复运行下述操作:在全部u属于U.v属于V-U的边(u,v)属于E中找到代价最小的一条边(u0,v0)并入集合TE,同一时候v0并入U,直至U=V为止.此时TE中必有n-1条边,T={V,{TE}}为N的最小生成树. 为实现此算法,需另设一个辅助数组closedge,以