zoukankan      html  css  js  c++  java
  • 最短路径的三种算法

    ps:给17级讲最短路径时候自己写的课件

    目录

    最短路径... 1

    概述: 1

    Floyd算法(弗洛伊德算法)复杂度O(n^3) 3

    Dijkstra算法(迪杰斯特拉算法)复杂度O(nlog2n) 5

    SPFA算法(Shortest Path Fast Algorithm的缩写) 12

    附录:... 12

    Floyd代码... 12

    Dijkstra O(n^2),链式前向星... 13

    Dijkstra + priority_queue + 链式前向星... 15

    最短路径

    概述:

     

    假设有一个图,点表示城市,边表示路径长度。

             如图,从点(2)到点(4)有若干总走法,比如

     

    (2)->(3)->(4), 这么走路径长度是4。

     

    也可以(2)->(3)->(1)->(4) 这样路径长度是15。当图有上万个点的时候就无法用人工直接求任意两点的路径,哪一条是最短的。最短路径就是解决这类问题。

    例题:http://www.fjutacm.com/Problem.jsp?pid=1499

    Floyd算法(弗洛伊德算法)复杂度O(n^3)

    const int inf = 0x3f3f3f3f;

    由于某些情况下避免正无穷加正无穷加成负无穷,所以正无穷有时不用0x7FFFFFFF, 用0x3f3f3f3f,或者1<<29, 1e9.

    这个算法只能用邻接矩阵存图, 我们先初始化一下矩阵

    void init() {   ///初始化矩阵

        for(int i = 1; i <= n; i ++ ) {

            for(int j = 1; j <= n; j ++ ) {

                if(i == j) {

                    mp[i][j] = 0; ///自己到自己的距离0

                } else {

                    mp[i][j] = inf;///否则都是inf

                }

            }

        }

    }

    然后读入一条u->v权值为w的边。对于矩阵mp[u][v]记录的是u->v的边的长度。对于无向图,mp[u][v]和mp[v][u]是相等的。而因为我们求的是最短路, u->v的路有多条我们只要存最短的。

    scanf("%d %d %d", &u, &v, &w);

    mp[u][v] = mp[v][u] = min(mp[u][v], w);

    存好矩阵后我们跑floyd算法,核心代码就这几行

    for(int k = 0; k < n; k ++ ) {

        for(int i = 0; i < n; i ++ ) {

            for(int j = 0; j < n; j ++ ) {

                mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]);

            }

        }

    }

    然后矩阵mp[s][t]的值就是s->t的最短路径。对于这题来说就是

    printf("%d ", mp[s][t] == inf ? -1 : mp[s][t]);

    下面我们来解释一下floyd的三层循环。

     

    我们发现(i)->(j)的最短路径可能是之间i->j,也可能是经过中间点i->k->j。

    如果我们用dp[i][j]表示u->v的最短路径。那么

    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

    然后看循环。

    当k = 0时

    枚举I,j.我们尝试让编号0的中点。优化我们的最短距离。

    矩阵成为了经过0点优化过的最短距离。

    K = 1时,我们又枚举I,j。让矩阵成为经过中间点0,1的最短路矩阵。

    以此类推

    。。。。

    来自知乎的解释

     

    优点:好写,核心代码总共就4行。经常扩展出各种图上动态规划

    缺点:复杂度高。点的数量1000的图就容易TLE

    Dijkstra算法(迪杰斯特拉算法)复杂度O(nlog2n)

     

    我们先有张图,我们要求1->5的最短路径是什么。

    换个问题,我们要求一个dist数组,dist[i]表示起点到i的最短路径。

    如果起点是1,初始化dist[1] = 0,其余都是正无穷

     

    我们的dist[]数组已经完成了dist[1](蓝色),和1相连的有两条边。

     

    我们更新dist数组后结果如上。

    Dist[2] = min(dist[2], dist[1]+(1->2的权值))

    Dist[3] = min(dist[3], dist[1]+(1->3的权值))

    我们从数组中找一个最小的数值。上图中的点2,那么dist[2]已经可以确定了。因为边最短,走其他的路绕回来肯定路更长。

     

    通过点2的边,我们可以更新dist数组,如果边能使得dist数值变小

    (2)能到达3和4.

    Dist[4]=min(dist[4], dist[2] + (2->4的权值))

    Dist[3]=min(dist[3], dist[2] + (2->3的权值))

     

    我们发现原来的dist[3]=4被dist[2]+(2->3的边)=3覆盖

    Dist[4]=inf被dist[2]+(2->4的边)=7覆盖。

    然后我们在选一条最短边。

     

    把点3加入集合。扩展的两条边更新dist数组。

     

    继续找最小

     

    Dist数组计算完成。

    在数据集合中选最小的数我们用优先队列logn,每次都能确定一个点,总过n个点。总复杂度O(nlogn)

    写法和大部分和周三的prim算法几乎一模一样的,也分O(n^2)的版本和O(nlogn)优化的版本。一般肯定写快的O(nlogn)的;

    //比较挫的O(N^2)版本,可能比较好理解

    int dist[maxn], vis[maxn];

    int dijkstra(int s, int t) { //从s->t的最短路径,无法到达反回-1

        for(int i = 0; i < n; i ++ ) {

            dist[i] = inf, vis[i] = 0;

        }

        dist[s] = 0;

        for(int k = 1; k <= n; k ++ ) { ///dist数组n个,每次确定一个值

            vis[s] = 1;

            for(int i = first[s]; ~i; i = edge[i].next) { ///更新dist数组

                int to = edge[i].to, w = edge[i].w;

                if(!vis[to] && dist[to] > dist[s] + w) {

                    dist[to] = dist[s] + w;

                }

            }

            int mincost = inf;

            for(int i = 0; i < n; i ++ ) { ///找最小

                if(!vis[i] && dist[i] < mincost) {

                    mincost = dist[i];

                    s = i;

                }

            }

        }

        if(dist[t] == inf) {

            return -1;

        } else {

            return dist[t];

        }

    }

    //优先队列实现

    struct Node {

        int to, cost;

        Node() {}

        Node(int tt, int cc):to(tt), cost(cc) {}

        friend bool operator < (const Node &a, const Node &b) {

            return a.cost > b.cost;

        }

    };

    int dist[maxn], vis[maxn];

    int dijkstra(int s, int t) {

        for(int i = 0; i < n; i ++ ) {

            dist[i] = inf, vis[i] = 0;

        }

        priority_queue<Node>que;

        que.push(Node(s, 0));

        /**

        丢进去一个to=s,cost=0的结构体

        等价于下面三行,看不懂往下翻附录构造函数

        struct Node tmp;

        tmp.to = s, tmp.cost = 0;

        que.push(tmp);

        */

        while(!que.empty()) {

            Node now = que.top();

            que.pop();

            if(!vis[now.to]) {

                vis[now.to] = 1;

                dist[now.to] = now.cost;

                for(int i = first[now.to]; ~i; i = edge[i].next) {

                    int to = edge[i].to, w = edge[i].w;

                    if(!vis[to]) {

                        que.push(Node(to, now.cost + w));

                    }

                }

            }

        }

        if(dist[t] == inf) {

            return -1;

        } else {

            return dist[t];

        }

    }

    SPFA算法(Shortest Path Fast Algorithm的缩写)

    Ps: SPFA也叫bellman ford的队列优化。但是bellman ford的复杂度比较高。SPFA的平均复杂度是O(n*log2n),复杂度不稳定,在稠密图(边多的图)跑的比dijkstra慢,稀疏图(边少的图)跑的比Dijkstra快。在完全图达到最坏的平方级复杂度。我们学它是因为有些图的边的长度是负数。这时候dijkstra就GG了。

    完全图: n个点中如果每个点都与其他n-1有边,无向图中,就有n*(n-1)/2条边。

    我们要求一个dist[]数组表示起点,到其他点的最短路径。

     

    我们有一条u->v的边,s是我们的起点。那么如果. S->V可以通过s->u,u->v来缩短距离那么就更新。我们称之为【松弛】

    我们可以每次对图中每条边u->v都拿出起点,松弛。直到每一次操作都不会改变dist数组的时候。Dist数组就是答案。(可以证明这样最多我们只要进行n-1次操作,每次操作拿出全部的m条边松弛,复杂度O(n*m),这就是bellman ford)。

    但是,起点附近的松弛必定会影响其他点,起点没松弛完,终点附近的变的松弛可以说是无效的。简单来说,近的点的dist还没确定,就用这个未确定的dist取更新远的点,浪费时间。

    所以我们需要一个队列优化。

    在队列中的点,需要松弛。我们先把起点丢进队列。具体看代码。

    Dist数组记录距离,inq数组标记是否在队列

    int SPFA(int s, int t) {

        int dist[maxn], inq[maxn];

        for(int i = 0; i < n; i ++ ) {

            dist[i] = inf, inq[i] = 0;

        }

        queue<int>que;

        que.push(s), inq[s] = 1, dist[s] = 0;

        while(!que.empty()) {

            int now = que.front();

            que.pop();

            inq[now] = 0;

            for(int i = first[now]; ~i; i = edge[i].next) { //每次拿出一个点开始松弛。

                int to = edge[i].to, w = edge[i].w;

                if(dist[to] > dist[now] + w) { //这个if看下面的图

                    dist[to] = dist[now] + w;

                    if(!inq[to]) { //松弛过的点dist变换了,可能影响其他的点。需要继续松弛

                        inq[to] = 1;

                        que.push(to);

                    }

                }

            }

        }

        return dist[t] == inf ? -1 : dist[t];

    }

     

    附录:

    构造函数

    平常我们写结构体这么写

    struct Point {

        int x, y;

    };

    Point a; //c++才能这么写,c语言中要struct Point a;或者typedef后才能直接point a;

    这是结构体a的值不确定。

    struct Point {

    int x, y;

    Point() {}

    };

    其实它默认有一个没有返回值,和结构体同名的函数,什么都不干。如果我们给他加点东西

    struct Point {

    int x, y;

    Point() { x = 0; y = 0};

    };

    这样Point a;那么a.x和a.y都是0.

    如果我们这么写

    struct Point {

    int x, y;

    Point() { x = 0; y = 0};

    Point(int xx, int yy) {

             X = xx;

             Y = yy;

    }

    };

    那么Point a(2, 3), 点a的x就是2,y就是3。

    所以 queue<Point >que; que.push(Point(2, 3));能直接往队列丢一个(2,3)的结构体

    Floyd代码

    #include <cstdio>

    #include <cstring>

    #include <algorithm>

    #include <iostream>

    using namespace std;

    const int maxn = 1005;

    const int inf = 0x3f3f3f3f;

    int n, m, mp[maxn][maxn];

    void init() {

        for(int i = 0; i < n; i ++ ) {

            for(int j = 0; j < n; j ++ ) {

                if(i == j) {

                    mp[i][j] = 0;

                } else {

                    mp[i][j] = inf;

                }

            }

        }

    }

    int main() {

        int u, v, w;

        while(~scanf("%d %d", &n, &m)) {

            init();

            for(int i = 1; i <= m; i ++ ) {

                scanf("%d %d %d", &u, &v, &w);

                mp[u][v] = mp[v][u] = min(mp[u][v], w);

            }

            for(int k = 0; k < n; k ++ ) {

                for(int i = 0; i < n; i ++ ) {

                    for(int j = 0; j < n; j ++ ) {

                        mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]);

                    }

                }

            }

            int s, t;

            scanf("%d %d", &s, &t);

            printf("%d ", mp[s][t] == inf ? -1 : mp[s][t]);

        }

        return 0;

    }

    Dijkstra O(n^2),链式前向星

    #include <cstdio>

    #include <cstring>

    #include <algorithm>

    #include <iostream>

    #include <queue>

    using namespace std;

    const int maxn = 1005;

    const int inf = 0x3f3f3f3f;

    int n, m, first[maxn], sign;

    struct Edge {

        int to, w, next;

    } edge[maxn * 2];

    void init() {

        for(int i = 0; i < n; i ++ ) {

            first[i] = -1;

        }

        sign = 0;

    }

    void add_edge(int u, int v, int w) {

        edge[sign].to = v;

        edge[sign].w = w;

        edge[sign].next = first[u];

        first[u] = sign ++;

    }

    int dist[maxn], vis[maxn];

    int dijkstra(int s, int t) {

        for(int i = 0; i < n; i ++ ) {

            dist[i] = inf, vis[i] = 0;

        }

        dist[s] = 0;

        for(int k = 1; k <= n; k ++ ) { ///dist数组n个,每次确定一个值

            vis[s] = 1;

            for(int i = first[s]; ~i; i = edge[i].next) { ///更新dist数组

                int to = edge[i].to, w = edge[i].w;

                if(!vis[to] && dist[to] > dist[s] + w) {

                    dist[to] = dist[s] + w;

                }

            }

            int mincost = inf;

            for(int i = 0; i < n; i ++ ) { ///找最小

                if(!vis[i] && dist[i] < mincost) {

                    mincost = dist[i];

                    s = i;

                }

            }

        }

        if(dist[t] == inf) {

            return -1;

        } else {

            return dist[t];

        }

    }

    int main() {

        int u, v, w;

        while(~scanf("%d %d", &n, &m)) {

            init();

            for(int i = 1; i <= m; i ++ ) {

                scanf("%d %d %d", &u, &v, &w);

                add_edge(u, v, w);

                add_edge(v, u, w);

            }

            int s, t;

            scanf("%d %d", &s, &t);

            printf("%d ", dijkstra(s, t));

        }

        return 0;

    }

    Dijkstra + priority_queue + 链式前向星

    #include <cstdio>

    #include <cstring>

    #include <algorithm>

    #include <iostream>

    #include <queue>

    using namespace std;

    const int maxn = 1005;

    const int inf = 0x3f3f3f3f;

    int n, m, first[maxn], sign;

    struct Edge {

        int to, w, next;

    } edge[maxn * 2];

    void init() {

        for(int i = 0; i < n; i ++ ) {

            first[i] = -1;

        }

        sign = 0;

    }

    void add_edge(int u, int v, int w) {

        edge[sign].to = v;

        edge[sign].w = w;

        edge[sign].next = first[u];

        first[u] = sign ++;

    }

    struct Node {

        int to, cost;

        Node() {}

        Node(int tt, int cc):to(tt), cost(cc) {}

        friend bool operator < (const Node &a, const Node &b) {

            return a.cost > b.cost;

        }

    };

    int dist[maxn], vis[maxn];

    int dijkstra(int s, int t) {

        for(int i = 0; i < n; i ++ ) {

            dist[i] = inf, vis[i] = 0;

        }

        priority_queue<Node>que;

        que.push(Node(s, 0));

        while(!que.empty()) {

            Node now = que.top();

            que.pop();

            if(!vis[now.to]) {

                vis[now.to] = 1;

                dist[now.to] = now.cost;

                for(int i = first[now.to]; ~i; i = edge[i].next) {

                    int to = edge[i].to, w = edge[i].w;

                    if(!vis[to]) {

                        que.push(Node(to, now.cost + w));

                    }

                }

            }

        }

        if(dist[t] == inf) {

            return -1;

        } else {

            return dist[t];

        }

    }

    int main() {

        int u, v, w;

        while(~scanf("%d %d", &n, &m)) {

            init();

            for(int i = 1; i <= m; i ++ ) {

                scanf("%d %d %d", &u, &v, &w);

                add_edge(u, v, w);

                add_edge(v, u, w);

            }

            int s, t;

            scanf("%d %d", &s, &t);

            printf("%d ", dijkstra(s, t));

        }

        return 0;

    }

    SPFA链式前向星

    #include <cstdio>

    #include <cstring>

    #include <iostream>

    #include <algorithm>

    #include <queue>

    using namespace std;

    const int maxn = 205;

    const int maxm = 1005;

    const int inf = 0x3f3f3f3f;

    int n, m, first[maxn], sign;

    struct Edge {

        int to, w, next;

    } edge[maxn * maxn];

    void init() {

        for(int i = 0; i < n; i ++ ) {

            first[i] = -1;

        }

        sign = 0;

    }

    void add_edge(int u, int v, int w) {

        edge[sign].to = v;

        edge[sign].w = w;

        edge[sign].next = first[u];

        first[u] = sign ++;

    }

    int SPFA(int s, int t) {

        int dist[maxn], inq[maxn];

        for(int i = 0; i < n; i ++ ) {

            dist[i] = inf, inq[i] = 0;

        }

        queue<int>que;

        que.push(s), inq[s] = 1, dist[s] = 0;

        while(!que.empty()) {

            int now = que.front();

            que.pop();

            inq[now] = 0;

            for(int i = first[now]; ~i; i = edge[i].next) {

                int to = edge[i].to, w = edge[i].w;

                if(dist[to] > dist[now] + w) {

                    dist[to] = dist[now] + w;

                    if(!inq[to]) {

                        inq[to] = 1;

                        que.push(to);

                    }

                }

            }

        }

        return dist[t] == inf ? -1 : dist[t];

    }

    int main()

    {

        while(~scanf("%d %d", &n, &m)) {

            init();

            for(int i = 1; i <= m; i ++ ) {

                int u, v, w;

                scanf("%d %d %d", &u, &v, &w);

                add_edge(u, v, w);

                add_edge(v, u, w);

            }

            int s, t;

            scanf("%d %d", &s, &t);

            printf("%d ", SPFA(s, t));

        }

        return 0;

    }

  • 相关阅读:
    助力APP尽情“撒币”!阿里云正式上线移动直播问答解决方案
    Linux API的fork()测试
    完美搞定《DOCKER IN ACTION》第二章示例
    docker+python无头浏览器爬虫
    阿里云播放器SDK的正确打开方式 | Aliplayer Web播放器介绍及功能实现(三)
    11月9日云栖精选夜读:阿里90后工程师_如何用AI程序写出双11打call歌?
    知名网站的 404 页面长啥样?你的404长啥样?
    10月24日云栖精选夜读:2017杭州•云栖大会完美收官 虚拟化平台精彩回顾
    memcache漏洞你补上了吗
    5分钟用Jitpack发布开源库
  • 原文地址:https://www.cnblogs.com/Q1143316492/p/8904487.html
Copyright © 2011-2022 走看看