最短路(更新中)

P.S.:本文知识点:

  • Floyd
  • Dijkstar
  • Bellman-Ford

我们在前面讲过图的遍历,今天我们来说说最短路


1:只有五行的算法你知道吗

别惊讶,真的!这个代码的核心部分只有五行!!
介绍一下他的名字:Floyd-Warshall,简称 Floyd,由Robert W.FloydDtephen 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),貌似不太友好,我们可不可以用队列优化一下呢?这个问题给读者自己思考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值