现在的位置: 首页 > 综合 > 正文

最小生成树之Prim

2017年04月28日 ⁄ 综合 ⁄ 共 2353字 ⁄ 字号 评论关闭

也谈不上多懂,刚刚看了一下,自觉略懂略懂,因而记之,望有助于同志者。

先讲一讲最小生成树。。

现在我给你n个点,m条边(a,b,c a,b 为边的两端,c 为长度),

请你得出最佳方案使这n个点连在一起的边的权值之和最小(总长度最小)。

PS:一条边两边都可以走,不是单方向的~~~ =。=   

称之为无向图~~相对应的有有向图,就是一条边有方向限制~~~  =。=

最小生成树就是这么一个总长度最小的图(我也不知道为什么叫树)。。。。。

很容易可以看出只需要(n-1)条边吧。(为什么?)(不用多说)

也可以看出肯定没有环吧(不用多说)

也可以看出它只能用于无向图吧。~~~

OK最小生成树略微介绍一下。进入下一环节~~

★★★★★★★★★★★★★★★★★★★★★★★★★★★★

现在开始介绍prim算法

我们把每一个点看做一个集合,,那么最终的结果就是所有集合 合到了一起。。

我们从点A开始考虑

一个点 A 和 能直接相连的点(就是有一条直接相接的路的,不用拐弯的),

最少也有一条边相连(为什么?)(不用多说。。)

所以如果 A 与周围的点中 A->B 是最短的,那么 A,B 肯定是连通的

为什么?我决定讲一下。。

{

我们把A点去掉,现在图中没有A点了,他的最小生成树中,每一个点都在树中,现在加入A点,为了使总权值最小,

那么连通A点的边要尽量小,所以………………(不用多说)

}

所以如果 A 与周围的点中 A->B 是最短的,那么 A,B 肯定是连通的

于是乎,首先,把这条边扫进来。现在A,B连在一起了

但A,B不可能是孤立的啊,A,B肯定还和其他点连在一起(不用多说。。。。。)

我们简称(A,B)这个集合为S。。。所以S周围有很多边与S相连。。我们要总权值最小啊!!

所以选择其中与S相连的最短的边,拉进来!

现在S集合有三个点了。。S集合不是孤立的……在从周围贪心选择最短边,拉进来

当所有点都在这个集合中,,prim就执行完毕了。。

这样一个图,,我们随便选个点a1吧,,a1与周围的点中,距a2最近,只有1,所以把a2拉进来,,

貌似a4a5之间没有啊。。。。那就假定他无限大吧~~~=。=



现在a1 a2是一家了,距离a1a2最近的点有两个,a3,a4,这个就随便选一个呗,那就a3吧,把a3拉进来,,


接下来,距离a1a2a3最近的就是a4了,


本人绘图能力有限,之后同理可得~~~

可见如果存在边相同的情况,最小生成树可能是不同的(但其总权值相同)(为什么??都是贪心得到的噻~~)

讲到这里估计你也搞懂prim算法的基本原理了,

如何实现呢??????

边的储存,,随你啦,一般是邻接矩阵or邻接表

邻接矩阵就是一个n*n的数组,p[i][j]就是i-j的长度

邻接表就是一个链表,每个节点储存了这条边的起点终点,长度,以及上一条边的位置

(为什么不储存下一条边的位置??因为插入边的时候还得找到上一条边,即使再加个数组实时记录每个点最新边的位置,

也还要一个数组记录每个点最老边的位置,如果储存上一条边就没这么麻烦了)

PS:边太多不好哦~~=。=

储存好了,,就开始扫把,

从第一个点开始,扫到最短相邻边,记录边长,把点标记为已走,然后把与该点相邻且终点未走的边拉进来,

再扫到最短边,在拉进来…………

当然,如果你还嫌时间慢的话~~~

那就优化吧:建一个堆,扫一次就把边放进去~~,取得时候判断有没有到达过,到过就继续取,没到过就走这条路啦!!~~

不过貌似建堆的代码挺长的额。。。=。=

也有另一种思路(仅供大家参考啊~~刚想到的~~ =。= ):对每个点的相邻边按大小排序(终点得跟着动啊~~~),

新建一个数组 p[MAX] 记录每个点周围的边中 已走的个数,(就是已走到的位置),

然后同样取边,第i个点的边取到了,那么p[i]++,寻求集合周围最大边时,

就只要从每个点周围的ROAD[i][ p[i]+1 ] (邻接矩阵) 中找就行了,

找的时候,如果这条边是不可走的(终点走过),那就更新一下这个点的周围已走,相比朴素算法好多了,

相比堆也相差无几,而且可行性也略好,不过本屌没试过~~~


基本思想讲完了。。

prim是基于点扫描的算法,点少边多的时候可以用一用(为什么?它每个点只扫一遍啊。 =。 =)

有时候点多边少。。就得用另一种算法~~~kruskal算法,这个以后再讲~~~

附上刚写的代码一段~~~(朴素算法~~)

int prim()
{
	int ans = 0;
	int dist[n+1];
	bool vis[n+1];
	for (int i = 1; i <= n; i++)
	{//将所有点距1点的距离更新,,标记所有点未走 
		vis[i] = false;
		if ( road[1][i] != 0 )
		{
			dist[i] = road[1][i];
		}
		else
		{
			dist[i] = 99999;//很大很大 
		}
	}
	dist[1] = 0;//设第一个点未走。。。 
	for (int i = 1; i <= n; i++)//没走一次拉入一个点,没有把1号特殊处理=。= 
	{
		int mini = 99999;
		int end = 0;//mini是记录最小值的,end是记录终点 
		for (int j = 1; j <= n; j++)
		{//寻找最短路 
			if ( vis[j] == false && mini > dist[j] )
			{
				mini = dist[j];
				end = j;
			}
		}
		vis[end] = true;//标记已走 
		ans += mini;//更新长度 
		for (int j = 1; j <= n; j++)
		{//更新周边点的最短路径 
			if ( road[end][j] != 0 && road[end][j] < dist[j] )
			{
				dist[j] = road[end][j];
			}
		} 
	}
	return ans;
}

虽然有点丑陋啊~~而且正确性也无法考证。。。=。=

有的时候点多边少~~prim就不适合了

讲完了

大牛勿喷。。。

=。=

----------------------------------END



抱歉!评论已关闭.