853. 有边数限制的最短路

时间:2021-09-22
本文章向大家介绍853. 有边数限制的最短路,主要包括853. 有边数限制的最短路使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目传送门

Bellman-Ford算法

算法思路:

for n次
for 所有边 a,b,w 表示存在一条a->b 的边,权重是 w
dist[b]=min(dist[b],dist[a]+w)

结束!

概念:了解即可
对于所有的边都满足 dist[b]<=dist[a]+w ,这个叫三角不等式,更新操作叫松弛操作。

Bellman_Ford适用于负权边的,

有负权回路的话,最短路是不一定存在的。为什么是不一定,可以看图1和图2.
图1描述了一直走下去一直小,那么,最短路径就是负无穷,不存在最短路径。
图2描述了虽然有负权回路,但由于此负权回路不在1~n的路线中,不影响最短路,所以,也还存在最短路径。

Bellman_Ford是可以找负环的,但时间复杂度比较高,一般不用它来找负环。

Bellman_Ford能干的事,SPFA都能干,而且时间复杂度比Bellman_Ford要低。
但有一个例外,就是如果限制了边数的最短路,那么只能用Bellman_Ford算法。

算法逻辑

1)初始化所有点到源点的距离为∞,把源点到自己的距离设置为0;

2)不管3721遍历n次;每次遍历m条边,用每一条边去更新各点到源点的距离。

值得注意的是

  1. ⭐️需要把dist数组进行一个备份,这样防止每次更新的时候出现串联;

  2. ⭐️由于存在负权边,因此return -1的条件就要改成dist[n]>0x3f3f3f3f/2;

  3. ⭐️上面所谓的n次遍历的实际含义是当前的最短路径最多有n-1条边,这也就解释了为啥要i遍历到n的时候退出循环了,因为只有n个点,最短路径无环最多就存在n-1条边。

  4. ⭐️这里无需对重边和自环做单独的处理:
    1] 重边:由于遍历了所有的边,总会遍历到较短的那一条; 2] 自环: 有自环就有自环啊,反正又不会死循环;

  5. ⭐️令人愉悦的是,该算法无非就是循环n次然后遍历所有的边,因此不需要做什么特别的存储,只要把所有的边的信息存下来能够遍历就行;

6)⭐️bellman_ford算法可以存在负权回路,因为它求得的最短路是有限制的,是限制了边数的,这样不会永久的走下去,会得到一个解;

  1. ⭐️SPFA算法各方面优于该算法,但是在碰到限制了最短路径上边的长度时就只能用bellman_ford了,此时直接把n重循环改成k次循环即可

理解与感悟

(1)本题明确存在负权边,所以不能使用Dijkstra算法,Dijkstra只能用在正权边。

(2)有边数限制的最短路径
bellman - ford算法擅长解决有边数限制的最短路问题
这个有边数限制就很霸气了,其它算法没有这个功能!
比个现实中的场景:旅游N个城市,1->n没有直达的飞机,我们需要经其它的城市进行中转,
票价就是中转的机票票价之和,每次中转都会让人的心情不爽一点,也就是不能超过k次中转。

(3)Bellman_ford是可以用来判断负环的,但由于时间复杂度较高,一般使用SPFA来判断负环。

(4)什么情况下存在负环,还存在最短路径?
比如
上面这张图之所以不存在负环,是因为2号点不在1->n点的路径上,就是有负环,也无谓!

(5)、为什么需要backup[a]数组
为了避免如下的串联情况, 在边数限制为一条的情况下,节点3的距离应该是3,但是由于串联情况,利用本轮更新的节点2更新了节点3的距离,所以现在节点3的距离是2。

正确做法是用上轮节点2更新的距离--无穷大,来更新节点3, 再取最小值,所以节点3离起点的距离是3。

C++ 代码

#include <iostream>
#include <cstring>

using namespace std;
const int N = 510, M = 10010;

//bellman_ford算法可以使用结构体,不用使用邻接表,很牛B,为什么Dijkstra不行呢?
struct Edge {
    int a, b, w; //起点,终点和权重
} edges[M];

int n, m, k;
int dist[N];
int backup[N];

int bellman_ford() {
    //初始化
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < k; i++) { //不超过k条边的最短距离
        //把dist数组备份一下,只用上一次迭代的结果,防止出现串联,因为本次迭代可以造成几个节点数据的修改
        memcpy(backup, dist, sizeof dist);

        for (int j = 0; j < m; j++) {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], backup[e.a] + e.w);
        }
    }
    return dist[n];
}

int main() {
    //输入
    scanf("%d%d%d", &n, &m, &k); //不超过k次
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }
    //调用bellman_ford算法 
    int res = bellman_ford();
     //为什么要用dist[n]>0x3f3f3f3f/2? 这可能是因为在操作过程中dist[n]被更新成0x3f3f3f3f小一点的数,其实还是无法到达
    if (res > 0x3f3f3f3f / 2) puts("impossible");
    else printf("%d\n", res);
    return 0;
}

原文地址:https://www.cnblogs.com/littlehb/p/15319764.html