图论--最短路径--Floyed算法,Dijkstra算法

最短路径算法

给定一个有向图或无向图 G(V,E) 有时我们需要求某个结点到其他所有结点的最短路径的长度,有时需要求出任意两个结点的最短路径。(如果是指定的两个结点间的最短路径可以直接暴搜,思维难度较低)。
求某个结点到其他所有结点的最短路径的长度的算法,叫单源最短路径算法,后者叫全源最短路径算法。

全源最短路径–Floyed算法

设图 G(V,E)(有向无向都无所谓,Floyed算法不关心,下面Dijkstra同)
用邻接矩阵来保存。设dis[i][j] 为结点 i 与结点 j 之间的距离,如果i与j之间没有边相连,则初始化dis[i][j] 为 INF。

我们能想到,结点i和j之间可能有一条边,但是这条边的长度不一定是i,j之间的最短路,因为 i 结点有可能经过其他结点的中转到达 j,有可能存在更短的道路。
那么对于dis[i][j]数组,我们可以有另一种理解方式,不再是两结点的距离,而是不允许经过任何结点中转的 i 与 j 之间的最短路径。这个最初始的距离,我们暂时将他视为最短距离,只不过这个距离只能是 i 和 j 的直接距离。
但是在实际中,i到达j是可以在别的结点进行中转的,甚至可能经过了很多结点的中转才能得到 i 到 j 的真正的最短路
假如在上例中,我们只允许结点 1 作为中间点,来更新任意两个结点之间的这个“伪最短路径”。也就是说对于每个结点对(i,j),检查:
i ----> 1 -----> j ** 这条路径,是否比直接 i ------> j 更短,
那么,我如果想让结点 2 也参与中转呢?很简单,刚才 “只能用结点1中转” 的结果已经保存在dis[i][j]里面了,这时候只要直接再用结点2进行一次中转,得到的dis[i][j],就是“可以用结点1和结点2两个结点进行中转的,i 和 j 的 最短距离”,此时还是“伪最短距离”。
也就是说,这个时候,任意两个结点的“最短距离”,不再是i,j的直接距离,而是:可以走1,可以走2,可以两个都走,反正1和2两个结点都能用了。
这样一来,如果我想得到任意两点的 “真最短距离” ,只需要依次把所有结点都加入到 “可中转结点” 中就行了。
见代码:

for(int k=1;k<=n;k++)              //最外面枚举中转结点,n是结点个数
    for(int i=1;i<=n;i++)          //内部两层,尝试更新任意两点的最短路径
        for(int j=1;j<=n;j++)
            if(dis[i][k]+dis[k][j]<dis[i][j])
                dis[i][j] = dis[i][k]+dis[k][j];

最外面一层循环,执行到第 a 层的意义是,允许使用编号 <=a 的结点作为中转结点
很显然这个算法的时间复杂度是 O(n^3)

Floyed算法的变种

实际上Floyed算法还有一个名字叫 “Floyed-Warshall算法”,它可以用来求二元关系R的传递闭包
现在dis[i][j] 不在表示i 与 j 的距离,现在表示 “i 和 j R 相关”,在有向图中体现为有一条 i 到 j 的边(j 到 i 不一定,有向图)。在传递闭包中,u与v R相关,v 与 w 关于R相关,则 u 与 w 也R相关。
基本思想与前面的中转点思想一致,u 可能经过很多结点的 “相关”传递到w。具体见代码。

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(dis[i][k]+dis[k][j]<dis[i][j])
                dis[i][j] = dis[i][j] || (dis[i][k]&&dis[k][j]);

得到的dis[][] 就是有向图的传递闭包。

单源最短路径–Dijkstra

Dijkstra算法是单源最短路径算法,能求出任意一个源点到其他结点的最短路。
当你开始研究Dijkstra之前有一件事必须要了解,这个算法不同于其他最短路径算法,它只能求 “所有边权均不为负” 的有向 and 无向图的最短路,原因下面说。
用一个一维数组dis[],来保存源点到其他结点的最短路径。
Dijkstra算法的基本思想:
首先将所有结点分成两类,一类是已经确定了最短路径的结点,我叫它“白点”,第二类是还没确定最短路径的结点,我们叫它“蓝点”。下面都用下图来说明这个算法,我们令源点是 1 。首先将所有 dis[] 初始化为INF,因为此时所有点都是“蓝点”。然后将dis[1] = 0因为显然源点到源点的最短路就是0。然后开始算法。
在这里插入图片描述
首先我们找到此时距离源点最近的结点(也就是此时dis[u] 最小的u),直接将u从蓝点变成白点,然后枚举所有蓝点,并尝试用新 “白点”,更新蓝点的最短路径。第一轮显然只有源点一个结点变成白点,并且更新了蓝点2,3,4的dis[],在下一轮中,由于dis[2] = 2 最小,所以结点2被选出变成白点。
在这里插入图片描述
为什么是这样做?
原因就是Dijkstra巧妙的贪心思想
任何时候,只要你在 dis[] 数组中找到一个最小的 dis[u] ,那么这个dis[u],一定就是源点到u的最短路径
在这里插入图片描述
这个时候我也可以解释,为什么Dijkstra只能求 “没有负边权的最短路”。
有负边权的贪心是错的
有负边权的贪心是错的
有负边权的贪心是错的
在这里插入图片描述
即使现在 dis[u] 最小了,由于中转点到u可能存在负值,所以u根本不能确定能不能变成白点。
综上,Dijkstra的思想,就是不断在 dis[]里找最小,变成白点,用白点更新蓝点的dis[],再找最小… 这样一个流程。
第三轮把结点3变成了白点,把所有结点都变成白点算法就完成了,再做一张图,剩下的咕咕咕。。其实可以脑补了应该。。
在这里插入图片描述
代码:

memset(dis, 0x7f, sizeof(dis));          //dis初始化无限大
memset(vis, 0, sizeof(vis));             //vis用来标记白点
dis[1] = 0;                              //源点dis = 0 
for(int i=1;i<=n;i++)
{
	int minn = 2147483647, index;         //找最小的dis[u], index用来记录u 
	for(int j=1;j<=n;j++) 
	{
		if(!vis[j]&&dis[j]<minn)          //vis用来标记白点, 找最小的dis在蓝点里找 
		{
			minn = dis[j];
			index = j;
		}
	}
	vis[index] = 1;                 //index就是找到的dis[u]最小的u
	                                //把它变成白点 
	for(int j=1;j<=n;j++)
	{
		if(!vis[j]&&map[index][j]!=0)      //用白点更新蓝点, map[i][j]是邻接矩阵存图
		    if(dis[index]+map[index][j]<dis[j])
		        dis[j] = dis[index]+map[index][j];
	}
}

没有优化Dijkstra算法的时间复杂度是O(n^2)

涉及到本文知识的题目

Floyed传递闭包+拓扑排序:洛谷P2419 Cow Contest S
Floyed最短路:洛谷P1522 牛的旅行 Cow Tours
Dijkstra1:洛谷P3371 【模板】单源最短路径(弱化版)
Dijkstra2:洛谷P4779 【模板】单源最短路径(标准版)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值