图的应用—最小生成树


前言

终于迎来了图的应用这部分内容的学习啦,接下来将介绍普里姆算法和克鲁斯卡尔算法。若对文章内容有疑惑,欢迎评论区发言,小白会积极改进的。
参考文献:严蔚敏.吴伟民.数据结构(C语言结构)[M].北京:清华大学出版社,2011.


一、最小生成树

1.什么是最小生成树?

定义:在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树称为该联通网的最小代价生成树,简称为最小生成树。

知识点回顾

1.连通图的生成树

连通图的生成树:一个极小连通子图,它包含图中全部顶点,但只有足以构成一棵树的n-1条边,这样的连通子图称为连通图的生成树。
一棵有n个顶点的生成树有且仅有n-1条边。如果一个图有n个顶点和小于n-1条边,则是非连通图。如果它多于n-1条边,则一定有环。但是,有n-1条边的图不一定是生成树。
连通图的生成树如图示:
在这里插入图片描述

2.极大连通子图

子图是无向图的连通子图,将无向图的任何不在该子图中的顶点加入,子图就不再连通。

3.极小连通子图

子图是无向图的连通子图,在该子图中删除任何一条边,子图不在连通。

最小生成树应用举例:在n个城市之间建立通信联络网,在每两个城市之间设置一条线路,我们要付出相应的经济代价,求怎么在城市之间设置线路使得总花费最少?
可以用连通网来表示这n个城市,这n个城市之间最多可能设置n(n - 2)/2条线路。在n个城市之间建立通信联络网,只需要n-1条路就可以联络起来,考虑到总花费要最少,所以我们只需要设置n-1条线路,这样可以对这n个顶点建立许多不同的生成树并且总花费要最少,那么最合理的通信网应该是代价之和最小的生成树。这就需要我们求最小代价生成树,也就是最小生成树。

2.MST的性质

构造最小生成树有多种算法,其中多数算法利用了最小生成树的一种简称为MST的性质:
假设N = (V, E)是一个连通网,U是顶点集V的一个非空子集,若(u,v)是一条具有最小权值(代价)的边,其中u∈U、v∈V - U,则必存在一棵包含边(u, v)的最小生成树。

二、普里姆算法

1.普里姆算法的构造过程

假设N = (V,E)是连通网,TE是N上最小生成树中边的集合。
(1)U = {uo}(uo∈V),TE = {}。
(2)在所有u∈U、v∈V-U的边(u,v)∈E中找一条权值最小的边(uo, vo)并入集合TE,同时vo并入U。
(3)重复(2),直到U = V为止。
简单来说就是,任意选择一个顶点开始,然后挑选这个顶点与其他顶点相连边权值最小的那一条边,之后又找这两个顶点与其他顶点相连边权值最小的那一条边,重复下去,就可以形成一个最小生成树。

2.普里姆算法的实现

假设一个无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树T,要求输出T的各条边。为实现这个算法需附设一个辅助数组closedge,以记录从U到V-U具有最小权值的边。对于每个顶点vi∈V-U,在辅助数组中存在一个相应分量closedge[i - 1]它包括两个域:lowcost和adjvex。其中lowcost存储最小边上的权值,adjvex存储最小边在U中的那个顶点。显然,closedge[i - 1].lowcost = min{cost(ui, vi)},其中cost(u,v)表示赋予边(u, v)的权。

struct {
	VerTexType adjvex;	// 最小边在U中的那个顶点
	ArcType lowcost;	// 最小边上的权值
}closedge[MVNum];

3.普里姆算法

算法步骤:
首先将初始顶点u加入U中,对其余的每一个顶点vj,将closedge[j]均初始化未到u的边信息。
循环n - 1次,做如下处理:
从各组边closedge中选出最小边closedge[k],输出此边;
将k加入U中;
更新剩余的每组最小边信息closedge[j],对于V-U中的边,新增加了一条从k到j的边,如果新边的权值比closedge[j].lowcost小,则将closede[j].lowcost更新为新边的权值。
算法步骤

#define MaxNum 100

typedef struct {
	char vexs[MaxNum];	// 存储顶点信息的一维数组
	int arcs[MaxNum][MaxNum];	// 定义二维数组,表示邻接矩阵
	int vexnum, arcnum;	// 图的实际顶点数和边数
}AMGraph;

struct {
	char adjvex;	// 最小边在U中的那个顶点
	int lowcost;	// 最小边上的权值
}closedge[MaxNum];

int LocateVex(AMGraph &G, char v) {
	for(int i = 0; i < G.vexnum; i++) {
		if(v == G.vexs[i]) {
			return i;
		}
	}
	return -1;
}

// 无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树T,输出T的各条边
void MiniSpanTree_Prim(AMGraph G, char u) {
	int k = LocateVex(G, u);	// k为顶点u的下标
	for(int i = 0; i < G.vexnum; i++) {
		int j;
		if(j != k)
			closedge[j] = {u, G.arcs[k][j]};	// 对U-V的每一个顶点初始化closedge[j]
	}
	closedge[k].lowcost = 0;
	for(int i = 1; i < G.vexnum; i++) {
		k = closedge[0].lowcost;
		for(int j = 1; j < G.vexnum; j++) {
			if(closedge[j].lowcost < k && closedge[j].lowcost != 0) {
				k = closedge[j].lowcost;
			}
		}
		char u0 = closedge[k].adjvex;
		char v0 = G.vexs[k];
		closedge[k].lowcost = 0;
		for(int j = 0; j < G.vexnum; j++) {
			if(G.arcs[k][j] < closedge[j].lowcost) {
				closedge[j] = {G.vexs[k], G.arcs[k][j]};
			}
		}
	}
}

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

三、克鲁斯卡尔算法

1.克鲁斯卡尔算法的构造过程

假设连通网N = (V,E),将N中的边按权值从小到大的顺序排序。
(1)假设初始状态为只有n个顶点而无边按权值从小到大的顺序排序。
(2)在E中选择权值最小的边,若该边依附的顶点落在T中不同的连通分量上(不形成回路),则将此边加入T中,否则舍去此边而选择下一条权值最小的边。
(3)重复(2),直至T中所有顶点都在同一连通分量上为止。

2.克鲁斯卡尔算法的实现

算法步骤
(1)将数组Edge中的元素按权值从小到大排序。
(2)依次查看数组Edge中的边,循环执行以下操作:
依次从排好序的数组Edge中选出一条边(v1, v2);
在Vexset中分别查找v1和v2所在的连通分量vs1和vs2进行判断:
如果vs1和vs2不等,表明所选的两个顶点分别属于不同的连通分量,输出此边,并合并vs1和vs2两个连通分量;
如果vs1和vs2相等,表明所选的两个顶点属于同一个连通分量,舍去此边而选择下一条权值最小的边。
算法描述

void MiniSpanTree_ Kruskal(AMGraph G) {
	Sort(Edge);
	for(int i = 0; i < G.vexnum; i++) {
		Vexset[i] = i;
	}
	for(int i = 0; i < G.arcnum; i++) {
		v1 = LocateVex(G, Edge[i].Head);
		v2 = LocateVex(G, Edge[i].Tail);
		vs1 = Vexset[v1];
		vs2 = Vexset[v2];
		if(vs1 != vs2) {
			for(int j = 0; j < G.vexnum; j++) {
				if(Vexset[j] == vs2)
					Vexset[j] = vs1;
			}
		}
	}
}

总结

主要都是参考文献。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值