zoukankan      html  css  js  c++  java
  • 【算法简述】最短路

    图论问题概述总结

    对于**图论**,我们尊熟悉的算法是比较多的,这次,我就找了集中常用的算法。
    ## 几种算法

     1. **最短路**算法(Dijkstra,SPFE,FLOYD)

    - Dijkstra单源最短算法

    首先,此算法适用于计算一个点到另一个点的最短路径,且算法绝对不能出现负环。这个算法的速度慢,只用于接觉小规模的问题,如图:
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190609113100303.png)这个图就是求算法的基本思路。算法过程:

     - 从节点上找到最近点那个节点,将他标记,加入集合U。
     - 将定点U连出边的邻点相连接,不在集合U中寻找。
     - 重复前面的操作,用来指导V=U是,查找结束,最后结束流程。

    本算法的算法流程图:
    https://wenku.baidu.com/view/8a5c11303968011ca300916a.html
    参考代码:

    ```

    #include <iostream>
    #include <cstring>
    using namespace std;
    const int N = 1e3 + 9;
    const int M = 1e4 + 9;
    const int inf = 0x3f3f3f3f;
    struct edge {
    int v, w, next;
    edge() {}
    edge(int _v, int _w, int _next) {
        v = _v;
        w = _w;
        next = _next;
    }
    } e[M << 1];
    int head[N], len;
    void init() {
        memset(head, -1, sizeof head);
        len = 0;
    }
    void add(int u, int v, int w) {
        e[len] = edge(v, w, head[u]);
        head[u] = len++;
    }
    void add2(int u, int v, int w) {
        add(u, v, w);
        add(v, u, w);
    }
    int n, m;
    int dis[N];
    bool vis[N];
    void dijkstra(int u) {
    memset(vis, false, sizeof vis);
    memset(dis, inf, sizeof dis);
    dis[u] = 0;
    for (int i = 0; i < n; ++i) {
        int mi = inf;
        for (int j = 1; j <= n; ++j) {
            if (!vis[j] && dis[j] < mi) {
                mi = dis[u = j];
            }
        }    
    if (mi == inf) {
        return;
    }
    vis[u] = true;
                for (int j = head[u]; ~j; j = e[j].next) {
        int v = e[j].v;
        int w = e[j].w;
                    if (!vis[v] && dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
                    }
                }
            }
        }
    int main() {
    init();
    int u, v, w;
    cin >> n >> m;
    while (m--) {
        cin >> u >> v >> w;
        add2(u, v, w);
    }
    dijkstra(1);
    cout << dis[n] << endl;
    return 0;
    }


    ```
    这只是一个基本的流程代码,你可在刷掉模板的基础上,在进行修改。
    **它的基本思想是以起始点为中心往外层扩展(广度优先搜索+贪心),直到扩展到终点为止。**

    这就是我们的算法,因此,可以解决很多问题。

    ```
    3 3
    1 2 5
    2 3 5
    3 1 2
    ```
    输入数据,看看会怎么样! 

    -堆优化Dijkstra

    正对于稀疏图的算法,我们用与优化。

    ```

    #include <iostream>
    #include <cstring>
    #include <set>
    using namespace std;
    const int N = 1e3 + 9;
    const int M = 1e4 + 9;
    const int inf = 0x3f3f3f3f;
    typedef pair<int, int> pall;
    #define X first
    #define Y second
    struct edge {
    int v, w, next;
    edge() {}
    edge(int _v, int _w, int _next) {
    v = _v;
    w = _w;
    next = _next;
    }
    } e[M << 1];
    int head[N], len;
    void init() {
    memset(head, -1, sizeof head);
    len = 0;
    }
    void add(int u, int v, int w) {
    e[len] = edge(v, w, head[u]);
    head[u] = len++;
    }
    void add2(int u, int v, int w) {
    add(u, v, w);
    add(v, u, w);
    }
    int n, m;
    int dis[N];
    bool vis[N];
    void dijkstra(int u) {
    memset(vis, false, sizeof vis);
    memset(dis, inf, sizeof dis);
    dis[u] = 0;
    }
    int main() {
    init();
    int u, v, w;
    cin >> n >> m;
    while (m--) {
    cin >> u >> v >> w;
    add2(u, v, w);
    }
    dijkstra(1);
    cout << dis[n] << endl;
    return 0;
    }


    ```
    找最小值,这里是和普通 Dijkstra 的核心不同之处,我们只需要获取堆顶元素即可(堆自动实现排序,
    堆顶元素就是我们需要的最小值)。然后我们把这个元素加入集合(标记就是加入集合的意思)。
     

    - SPFA单源最短路算法

    在 SPFA 算法中,使用 di表示从源点到顶点 i的最短路,额外用一个队列来保存即将进行拓展的顶点
    列表,并用ingi 来标识顶点 i是不是在队列中。
    如图:
    ![](https://img-blog.csdnimg.cn/20190609150752915.png)
    SPFA 的空间复杂度为O(V),有点像这个稀疏图的步揍。他用队列来运行,因此,SPEA只是是在对列上升级以下,并不是那种十分多的。**在一定程度上,可以认为 SPFA 是由 BFS 的思想转化而来。从不含边权或者说边权为 个单位长度的图上的 BFS,推广到带权图上,就得到了 SPFA。**

        算法步揍:
     - 用一个队列来保存多个扩展的队列。
     - 用一个队列中取的一个元素,并且对其他点进行松弛。
     - 当所有不在队列的点都入了队列,则程序结束,算法进行完毕。

    ```

    bool inq[MAX_N];
    int d[MAX_N]; // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
    void spfa(int s) {
        memset(inq, 0, sizeof(inq));
        memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    inq[s] = true;
    queue<int> q;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = false;
        for (int i = p[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if (d[u] + e[i].w < d[v]) {
                d[v] = d[u] + e[i].w;
                if (!inq[v]) {
                    q.push(v);
                    inq[v] = true;
                    }
                }
            }
        }
    }
    ```
    算法的图:https://i.loli.net/2019/06/09/5cfcb3db1267062487.jpg
    上面的代码就是SPEA的全部结构,初始化与第一个算法一样的结构一样的。
    **判断负环**的SPEA算法:
    
    ```
    #include <iostream>
    #include <cstring>
    #include <queue>
    using namespace std;
    const int N = 1e3 + 9;
    const int M = 1e4 + 9;
    const int inf = 0x3f3f3f3f;
    struct edge {
        int v, w, next;
        edge() {}
        edge(int _v, int _w, int _next) {
            v = _v;
            w = _w;
            next = _next;
    }
    } e[M << 1];
    int head[N], len;
    void init() {
        memset(head, -1, sizeof head);
        len = 0;
    }
    void add(int u, int v, int w) {
        e[len] = edge(v, w, head[u]);
        head[u] = len++;
    }
    void add2(int u, int v, int w) {
        add(u, v, w);
        add(v, u, w);
    }
    int n, m;
    int main() {
    init();
    int u, v, w;
    cin >> n >> m;
    while (m--) {
        cin >> u >> v >> w;
        add2(u, v, w);
    }
    return 0;
    }


    ```
    接下来就可以运行程序了:
    ```
    3 3
    1 2 5
    2 3 5
    3 1 2
    ``` 

    - Floyd 多源最短路算法


    Floyd 算法是一种计算给定的带权图中任意两个顶点之间最短路径的算法。相比于重复执行多次单源最
    短路算法,Floyd 具有高效、代码简短的优势,在解决图论最短路题目时比较常用。
    **floyd 算法是解决负环(可以计算出任意两点之间的最短路)有向图或无向图的多源最短路问题。常
    用邻接矩阵存储,算法的时间复杂度 O(N2),空间复杂度O(N2) 。**

    算法方式:

     - 如果不经过第 个点,那么就是 dp[k-1][i][j]。
     - 如果经过第 个点,那么就是 dp[k-1][i][j]+dp[k-1][i][j]。

    所以就变成了:

    ```

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


    ```
    所以就变成了如下的代码:

    ```

    int g[N][N]; // 邻接矩阵存图
    int dp[N][N][N];
    void floyd(int n) {
        for (int k = 0; k <= n; ++k) {
            for (int i = 1; i <= n; ++i) {
                for (int j = 1; j <= n; ++j) {
                    if (k == 0) {
                        dp[k][i][j] = g[i][j];
                    } else {
                        dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]);
                    }
                }
            }
        }
    }
    ```
    我们写出最终的 Floyd 的形式,这也是常用的写法,优化了一维的空间。并且写法更加简单。这里要注
    意,枚举的中间点 一定要写在最外面。没有注意这一点,很容易把 3 个循环的顺序弄错了,那么结
    果也就是错的。
    刚才的分析得出:
    
    ```
    int g[N][N];
    void floyd(int n) {
        for (int k = 1; k <= n; ++k) {
            for (int i = 1; i <= n; ++i) {
                for (int j = 1; j <= n; ++j) {
                    g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
                }
            }
        }
    }


    ```
    算法到这里就完成了,接下来给大家介绍**差分约束系统**。

     -  差分约束系统

    我们在求解差分约束系统时,可以将其转化为图论中单源最短路(或最长路)问题。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190609154201381.png)
    途中有负环是,就可以这样。

    ```

    #include <iostream>
    #include <cstring>
    #include <queue>
    using namespace std;
    const int N = 1e3 + 9;
    const int M = 1e4 + 9;
    const int inf = 0x3f3f3f3f;
    struct edge {
        int v, w, next;
        edge() {}
        edge(int _v, int _w, int _next) {
            v = _v;
            w = _w;
            next = _next;
        }
    } e[M << 1];
    int head[N], len;
    void init() {
        memset(head, -1, sizeof head);
        len = 0;
    }
    void add(int u, int v, int w) {
        e[len] = edge(v, w, head[u]);
        head[u] = len++;
    }
    void add2(int u, int v, int w) {
        add(u, v, w);
        add(v, u, w);
    }
    int n, m;
    int dis[N], in[N];
    bool vis[N];
    bool spfa(int u) {
        memset(vis, false, sizeof vis);
        vis[u] = true;
        memset(dis, -1, sizeof dis);
        dis[u] = 0;
        memset(in, 0, sizeof in);
        in[u] = 1;
        queue<int> q;
        q.push(u);
        while (!q.empty()) {
            u = q.front();
            q.pop();
            vis[u] = false;
            for (int j = head[u]; ~j; j = e[j].next) {
                int v = e[j].v;
                int w = e[j].w;
                if (dis[v] < dis[u] + w) { // 求最长路,和求最短路相反
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v);
                        vis[v] = true;
                        ++in[v];
                        if (in[v] > n + 1) {
                            return true;
                    }
                }
            }
        }
    }
    return false;
    }
    int main() {
    init();
    int u, v, w, op;
    cin >> n >> m;
    while (m--) {
        cin >> op;
        cin >> u >> v >> w;
    }
    if (op == 1) {
        add(u, v, -w);
    }else if (op == 2) {
        add(v, u, w);
    }else {
        add(u, v, -w);
        add(v, u, w);
    }
    for (int i = 1; i <= n; ++i) {
        add(0, i, 0);
    }
    if (spfa(0)) {
        cout << "no" << endl;
    }else {
        for (int i = 1; i <= n; ++i) {
            cout << "x" << i << " = " << dis[i] << endl;
        }
    }
    return 0;
    }


    ```
    这就是差分约束,现在运行你的程序:

    ```
    4 3
    1 1 2 3
    2 3 2 2
    3 3 4 1
    ```
    ~~点个赞呗~~

    **本期图论结束,下期再见!**

  • 相关阅读:
    OOAD基本概念
    WEB开发中常用的正则表达式
    一像素的恩怨情仇!程序猿与设计狮之间的那些事儿
    技术负责人的三种角色
    Ping命令详解
    zip命令的用法
    U盘装系统系列三—-ghost系统安装教程
    U盘装系统系列二—-如何设置U盘启动
    U盘装系统系列一—-安装老毛桃U盘启动制作工具
    Vi命令详解
  • 原文地址:https://www.cnblogs.com/rebirth-death2019/p/11579983.html
Copyright © 2011-2022 走看看