P.S.:本文知识点:
- Floyd
- Dijkstar
- Bellman-Ford
我们在前面讲过图的遍历,今天我们来说说最短路
1:只有五行的算法你知道吗
别惊讶,真的!这个代码的核心部分只有五行!!
介绍一下他的名字:Floyd-Warshall,简称 Floyd,由Robert W.Floyd和Dtephen Warshall在1962分别独立发表了这个算法
具体怎么实现,且听我慢慢道来
大家应该都做过火车或者飞机。我们可以把火车的出发点和结束点、途中停站看成一个个点,把每个火车的轨道看成边,就把他看成了一个图
细心的小伙伴们会发现:有时候从一个点直接到另一个点,不如从另外的地方中转快!
那么这一个想法,就是Floyd算法的核心
首先来一个图:
1 2 2
1 4 4
1 3 6
2 3 3
3 4 1
3 1 7
4 3 12
4 1 5
我们现在需要求任意两个点之间的最短路,也叫作多源最短路问题
我们可以使用对每两个点都进行一次DFS或者BFS,便可以得到两点之间的最短路,复杂度O(n^2)
那么,还有没有更快的呢?
有时候从一个点直接到另一个点,不如从另外的地方中转快!
这句话是不是给你带来了点启发?
我们是不是可以用“允许通过k个点”的方式去一步步缩短距离?
完美!接下来是代码核心部分:
for(int k = 0; k < n; k ++){//允许通过第k个点中转
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j++){
if(e[i][j] > e[i][j] + e[i][k]){//当中转后的值小于中转前的值
e[i][j] = e[i][k] + e[k][j];
}
}
}
}
由于大括号不需要,可以省略,正好5行
是不是很帅!
Floyd-Warshall算法可以处理带负权边,但是不能处理带有负权回路的图。因为,在负权回路中两点可能没有最短路径!
例如这个图:
1 2 2
2 3 3
3 1 -6
你把它用Floyd跑出来,我就要考虑把你拉来我们家做做??
2:单源最短路来袭
我们把求一个点到所有其他点的最短路径佳作单源最短路,求这个问题的算法的作者名Edsger Wybe Dijkstar,算法名Dijkstar
首先来看看这个图:
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
使用一个数组e存储(^代表无穷大):
0 1 12 ∞ ∞ ∞
∞ 0 9 3 ∞ ∞
∞ ∞ 0 ∞ 5 ∞
∞ ∞ 4 0 13 15
∞ ∞ ∞ ∞ 0 4
∞ ∞ ∞ ∞ ∞ 0
我们求的是1号到其他点的最短路径,我们就先用一个数组dis去储存1号原点到其他原点的舒适路径(注:为了后边讲解方便,特地使用3排,创建dis数组只用中间一个就好了)
下标:0 1 2 3 4 5 6
数值:0 0 1 12 ∞ ∞ ∞
确定:0 0 0 0 0 0 0
这个dis数组里边的值我们叫:估计值,就是从1直接到n点的距离
既然我们要求1点到其他点的最短路径,我们肯定要找到最短的路径。通过观察发现,1->2只有这一条路,绝对没有比他更短的了。那么,对于一个没有负环的图,是绝对不可能通过第三点中转来剪短两点之间长度为1的距离,你再减就没了嘛~
既然到了2号,我们就看看2号有那几条边:2->3,2->4这两个
先看看2->3能不能让路程变短:
比较:dis[3] 和dis[2]+e[2][3]
发现:dis[3]=12,dis[2]+e[2][3]=10,dis[3] > dis[2]+e[2][3]
结论:通过中站,可以减少1->3的路程
操作:dis[3] = dis[2]+e[2][3]
嘿!有没有发现路程变短了!
对于以上的操作,有一个专业的术语叫做松弛
接着来进行操作:
比较:dis[4] 和dis[2]+e[2][4]
发现:dis[4]=∞,dis[2]+e[2][3]=4,dis[4] > dis[2]+e[2][4]
结论:通过中站,可以减少1->4的路程
操作:dis[4] = dis[2]+e[2][4]
目前看来,我们的dis是这样的:
下标:0 1 2 3 4 5 6
数值:0 0 1 10 4 ∞ ∞
布尔:1 1 1 1 1 0 0
后续操作,请你自己做~
(起码画一下图,想个3分钟哦~)
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
好,我们现在来展示一下板子:
for(int i = 1;i <= n-1; i++){//想想为什么n-1
//找到最近点
min=inf;//请事先把inf设定为一个边数*点数+1的值
for(int j = 1;j <= n;j ++){
if(book[j] == 0 && dis[j] < min){
min = dis[j]
u = j;
}
}
//松弛操作
book[u] = 1;//book数组是用来标记是否确定的
for(int v = 1;v <= n; v ++){
if(e[u][v] < inf){
if(dis[v] > dis[u] + e[u][v])//松弛操作
dis[v] = dis[u] + e[u][v];
}
}
}
新问题来了,这个算法的时空复杂度是(N^2 ),用堆把最近顶点这一步工作优化到O(logN)那么,对于一个稀疏图(M<(N^2)的图,稠密图相反)可以用邻接表来代替,使得整个复杂度O(M+V)logN
给出邻接表板子
int n,m;''
int u[6],v[6],w[6];//请根据实际情况通知大小,都要比max(m)大1
int first[5],next[6];//请根据实际情况通知大小,first=max(n)+1,next=max(m)+1
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
first[i] = -1;
for(int i = 1;i <= m;i ++){
scanf("%d%d%d",&u[i],&v[i],&w[i]);//普通的数据输入
//重点!
next[i] = first[u[i]];
forsy[u[i]] = i;
}
这里是数组实现发,大家可以百度更好的指针链表法
请大家手动画图,自行摸索邻接表的用途
使用邻接表,遍历和储存图的时间是O(M)
话说回来,他还是无法面对负权边。
那么,到底怎么办呢?
#3:Bellman-Ford 算法——负权也不怕!
首先,我们来看看他长啥样:
for(k = 1;k <= n;k ++)
for(i = 1;i <= m;i ++)
if( dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i]
什么?怎么跟Dijskstra这么相似!
还有,这不就是松弛嘛!
u,v,w这么熟悉,难道还要用邻接表?
恭喜你,你答对了
Bellman-Ford其实就是对每一个边进行松弛,不断松弛,松弛n-1次
给定一个图,请用上面的语句手动模拟一下,并回答一下问题:
1.n-1松弛结束之后能够求出最短路径吗?
2.为啥n-1次就好了?
给上图:
1 2 -3
1 5 5
2 3 2
3 4 3
4 2 5
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
思考
答案:不告诉你~
此外,Bellman-Ford还可以检测负权回路。如果一个图在n-1次松弛之后还有变化,那么说明这个图有负权回路。可以自己试着证明一下哦~
然后,Bellman-Ford的复杂度是O(MN),貌似不太友好,我们可不可以用队列优化一下呢?这个问题给读者自己思考