Dijkstra和Prim算法 【含数学证明】

#ACM&算法

不知是不是因为自己的抽象学习能力还是不够强,之前在翻阅别人博客学习这两个算法的时候,整个人一直处于懵逼状态。

分析了一下,发现之前存在的博客教程,大多趋向于方法讲解,学完以后有知其然不知其所以然的感觉。另一部分博客则偏向数学证明,语言严谨但理解起来确实要费一番功夫。所以打算尝试一下在下文里以解决一个问题的方式讲解这两个算法。

在敲算法之前先阐明一下这两个算法所能解决的问题,先从Dijkstra开始。

举个栗子

我们现在拿到了一张无向图,如下:
《Dijkstra和Prim算法 【含数学证明】》

这是一个由7个节点和10条边组成的无向图。每条路上的数字代表这条路的长度。 现在任选其中两个点,我们需要找到一条路把它们连起来,并使这条路尽可能的短。

根据动态规划的思路,我们把这个问题化解成一个较小的问题去解决。我们将挑选两个紧挨着(有边直接相连)的点,A和B。

显而易见,如果从A到B有一条路能联通的话,那这条路只有两种可能: 1,这条路只有一条边,直接连接A和B。 2,这条路有多条边,从A出发,经过其他点中转,到达B。

那我们分别来讨论。 这两种方法的共同点就是,都要从A点出发。而从A点出发的路是有限的。那也就是说,如果从A出发直达B的这条路是从A出发所有路中最短的一条,那么这条路一定是AB间的
最短路。 证明也很简单,因为
所有路的长度都是正数,所以如果有另一条路从A出发绕过某些点到达B的话,那它一定会在从A出发的所有路的长度的基础上再加上一些数(至少为0)。也就绝对不可能小于AB直接连接的长度。 如下:
《Dijkstra和Prim算法 【含数学证明】》

我们先把起点标记为0,意为起点到自己的距离为0。因为这一定是A到A的最短路,所以把所有和A连接的点的值更新为A点的值加上A到该点的路程距离。

接着,根据刚才的论述,我们确定B的值已经更新为A到B的最短路,故,我们对B点也进行上述操作。得到下图:
《Dijkstra和Prim算法 【含数学证明】》

在本图中,已经更新过值,但还未确定为最短路的节点有C/E/F,他们中值最小的是E,那我们可以确定E现在的值就是从A到E的最短路。 原因同上,我们已经把A和B所有能走的路尝试走过了,如果从C和F还有另一条路能绕过某些点到达E的话,那一定比E现在的值要大,因为C和F的值已经比E大了,加上一个大于等于零的数只会更大。

重复进行上述操作,我们最终会得到如下的图:
《Dijkstra和Prim算法 【含数学证明】》

这时候,我们会发现,图上未被确定为最短路的点里,值最小的就是目标点D了,那我们就可以得出,从A到D的最短路是现在D的值,即12。

而Prim算法所解决的问题与上面的略有不同。

我们现在依然是面对这张无向图:
《Dijkstra和Prim算法 【含数学证明】》

现在,我们需要从这些路径中挑选出几条,使得所有点都可以通过这些路相互连通,并要求这些路的总长度尽量短。

先从思路讲起,

既然是求全联通的最短路,那么只要是已经连在一起的点,我们都可以把它们看成同一个点,而它们直接连接的所有的边,都是这个点直接连接的边。

用例子解释一下:假如我们已经挑选了两条路连接了A、B、C三个点:
《Dijkstra和Prim算法 【含数学证明】》

我们通过红色的路连接了A、B、C三点,图中所有标为黄色的路就是这个连通体外部所有直接连接的路。那么,我们可以确定,为了使E点接入连通体,我们最少的花费一定是通过C-E这条路。 原因同上,如果通过其它路去连接的话,那这条路一定比现有的路还要长一些,所以当前标为黄色的路中最短的一条一定是下一步选择的最优解。

反复上述操作,我们能得到下图:
《Dijkstra和Prim算法 【含数学证明】》

即为该无向图的
最小生成树

附Dijkstra代码,题目是HDOJ 1874:

#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;
#define MAX 1e9;

int city, road;
long vis[205];

struct point{
	long dis=1e9;
	int conect[205][2];
	long i=0;
}node[205];


void dij(long be, long ed)
{
	if (vis[be]==1)//如果已经踩过了 就不管了 【退出】
	{
		return;
	}
	vis[be] = 1; //如果没踩过 就标记成 踩过了
	for (size_t i = 0; i < node[be].i; i++) // 对于所有的 联通 的 点 更新 注意这个min函数
	{
		node[node[be].conect[i][0]].dis = min(node[node[be].conect[i][0]].dis, node[be].dis + node[be].conect[i][1]);
	}
	long minn=1e9;//为 寻找 下一个根节点做准备
	int targ=1e9;
	for (size_t i = 0; i < city; i++)
	{
		if (minn>node[i].dis&&vis[i]==0)//找到 未踩过的  dis值最小的 点 作为根节点
		{
			minn = node[i].dis;
			targ = i;
		}
	}
	if (targ==1e9)//如果没有 【退出】
	{
		return;
	}
		dij(targ, ed);//否则 对这个根节点 重复上述操作。
}


int main()
{
	
	while (cin >> city >> road)
	{
		memset(vis, 0, sizeof(vis));
		for (size_t i = 0; i < city; i++)
		{
			node[i].dis = 1e9;
			node[i].i = 0;
		}
		while (road--)
		{
			long a, b,c;
			cin >> a >> b>>c;
			node[a].conect[node[a].i][0] = b;
			node[a].conect[node[a].i][1] = c;
			node[a].i++;
			node[b].conect[node[b].i][0] = a;
			node[b].conect[node[b].i][1] = c;
			node[b].i++; 
		}
		long be, ed;
		cin >> be >> ed;
		node[be].dis = 0;
		dij(be, ed);
		if (node[ed].dis==1e9)
		{
			cout << "-1\n";
		}
		else
		{
			cout << node[ed].dis << "\n";
		}
	}
}

Prim代码 题目是HDOJ 1863:

/*Author:	Res*/
/*Date: 16/2/16*/

/*使用邻接矩阵存图,点数查询,没有使用并查集*/

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>

using namespace std;
int con[1005][1005];//邻接矩阵
int confar[1005];//已连通的图到未联通点的距离,若距离为0为无法连通,距离为1e9为已联通
long long ans,node;//node是已联通的点的数量


int main()
{
	int be, ed,lenth,n,m,target;
	while (cin >> m >> n)
	{
		memset(con, 0, sizeof(con));//初始化
		memset(confar, 0, sizeof(confar));
		ans = 0;
		node = 1;
		target = 0;//是否有某个点无法连通

		if (m==0)
		{
			return 0;
		}
		for (size_t i = 0; i < m; i++)
		{
			cin >> be >> ed >> lenth;
			if (con[be][ed])//存入邻接矩阵,因为两点间有多条路,只存最短的一条。
			{
				con[be][ed] = min(con[be][ed], lenth);
			}
			else
			{
				con[be][ed] = lenth;
			}
			if (con[ed][be])
			{
				con[ed][be] = min(con[ed][be], lenth);
			}
			else
			{
				con[ed][be] = lenth;
			}
		}

		be = 1;
		confar[1] = 1e9;
		while (node != n)
		{
			for (size_t i = 1; i <= n; i++)
			{
				if (con[be][i]&&confar[i]!=1e9)//在该点的邻接表中搜索更近的路程,存入距离表confar中
				{
					if (confar[i])
					{
						confar[i] = min(confar[i], con[be][i]);
					}
					else
					{
						confar[i] = con[be][i];
					}
				}
			}
			int find = 1e9;
			for (size_t i = 1; i <= n; i++)
			{
				if (find>confar[i]&&confar[i]!=0)//在距离表中搜索最近的未使用的路
				{
					find = confar[i];
					be = i;
				}
			}
			if (find==1e9)
			{
				target = 1;
			}
			ans += find;//累加
			confar[be] = 1e9;
			node++;
		}
		if (target)
		{
			cout << "?\n";
		}
		else
		{
			cout << ans << "\n";
		}
	}
}

1,无向图:由若干点与边构成,每条边都连接着两个点,且每条边都可以双向联通的图。

2,上述两个算法,当遇到待选取的路距离相同时,可任选一个,对结果没有影响。

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/reskip/article/details/50682436
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞