最短路问题

注:我们这里定义 \(mof=0x3f3f3f3f\)\(MOF=0x3f3f3f3f3f3f3f3f\),表示最短路径的无穷大,\(|V|\) 表示图中顶点数,\(|E|\) 表示图中边数

\(Dijkstra\) 算法

1、原方法时间复杂度是 \(O(|V|^2)\) 的,是直接用 \(queue\) 来遍历每个点,没有任何优化,但是我们可以发现我们每次以最短路最短的那个点先执行遍历,就可以咋子某个时刻发现这个点不会更加优了,所以我们就可以对这个点进行标记了,可以发现选最小的那个点,相当于是利用了一个优先队列进行了 \(log|V|\) 的优化,所以这样时间复杂度就被我们降低至 \(O(|V|log|V|)\),空间复杂度 \(O(|V|)\)

2、时间复杂度:\(O(|V|log|V|)\),空间复杂度:\(O(|V|)\)

3、模版:洛谷P4779,升级题:洛谷P1144

4、\(dijkstra\) 有个缺点,就是不能处理负权,而处理负权就需要用到我们的 \(spfa\) 算法了,后面会介绍。

5、单源最短路\(O(|V|log|V|)\),多源最短路:\(O(|V|^2log|V|)\)

template<typename T>
struct Dijkstra{
    struct node{
        int v;
        T w;
        node(int v = 0, T w = 0) : v(v), w(w) {}
    };
    struct S{
        T id, dis;
        bool operator< (const S &t) const {
            return dis > t.dis;
        }
    };

    int n;
    vector<T> dist;
    vector<bool> vis;
    vector<vector<node>> g;

    Dijkstra() {}
    Dijkstra(int n_) : dist(n_ + 1, mof), vis(n_ + 1), g(n_ + 1) {
        n = n_;
    }

    inline void init(int n_) {
        n = n_;
        dist.assign(n_ + 1, mof);
        vis.assign(n_ + 1, 0);
        g.assign(n_ + 1, node());
    }

    inline void add(int u, int v, const T &w) {
        g[u].push_back({v, w});
    }

    inline void dijkstra(int beg) {
        priority_queue<S> q;
        dist[beg] = 0;
        q.push({beg, 0});
        while (!q.empty()) {
            auto [u, disu] = q.top();
            q.pop();
            if (vis[u]) {
                continue;
            }
            vis[u] = 1;
            for (auto [v, w] : g[u]) {
                if (w + dist[u] < dist[v]) {
                    dist[v] = w + dist[u];
                    q.push({v, dist[v]});
                }
            }
        }
    }
};

\(SPFA\) 算法

1、一种时间复杂度介于 \(|V|+|E|\)\(|V||E|\) 的算法,优点在于可以处理负权,缺点就是容易被卡到 \(|V||E|\),如果是没有负权的情况下,我们可以直接选择 \(Dijkstra\) 算法。

2、该算法思想是维护一个队列,用一个 \(vis\) 数组标记目前处在队列中的元素,如果这个元素的最短路被缩短了,则更新最短路,并且如果这个元素不在队列中,我们就可以直接将这个点入队,最后队列为空结束,另外如果这个图里面有环的话,且这个环里面有负边权,就会导致队列中一直有元素,结束不了,所以如果对于有环图得特别判断下是否有环,除非题目告诉你无环

3、模版:洛谷P3371,升级题:洛谷P5960

4、有向图跑 \(spfa\),如图:

image

5、一般只用于求单源最短路,多源最短路会用到后面说的 \(Johnson\) 算法,时间复杂度更优

template<typename T>
struct SPFA{
    struct node{
        int v;
        T w;
        node(int v = 0, T w = 0) : v(v), w(w) {}
    };

    int n;
    vector<T> dist;
    vector<bool> vis;
    vector<vector<node>> g;
    SPFA() {}
    SPFA(int n_) : dist(n_ + 1, mof), vis(n_ + 1), g(n_ + 1) {
        n = n_;
    }

    inline void init(int n_) {
        n = n_;
        dist.assign(n_ + 1, mof);
        vis.assign(n_ + 1, false);
        g.assign(n_ + 1, vector<node>());
    }

    inline void add(int u, int v, const T &w) {
        g[u].push_back({v, w});
    }

    inline void spfa(int beg) {
        queue<int> q;
        dist[beg] = 0;
        vis[beg] = true;
        q.push(beg);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            vis[u] = false;// 出队
            for (auto &[v, w] : g[u]) {
                if (w + dist[u] < dist[v]) {
                    dist[v] = w + dist[u];// 更新最小值
                    if (!vis[v]) {
                        vis[v] = true;// 入队
                        q.push(v);
                    }
                }
            }
        }
    }
};

6、另外,\(spfa\) 还可以找是否存在负环:

image

代码如下:

template<typename T>
struct SPFA{
    struct node{
        int v;
        T w;
        node(int v = 0, T w = 0) : v(v), w(w) {}
    };

    int n;
    vector<T> dist;
    vector<int> cnt;
    vector<bool> vis;
    vector<vector<node>> g;
    SPFA() {}
    SPFA(int n_) : dist(n_ + 1, mof), cnt(n_ + 1), vis(n_ + 1), g(n_ + 1) {
        n = n_;
    }

    inline void init(int n_) {
        n = n_;
        dist.assign(n_ + 1, mof);
        vis.assign(n_ + 1, false);
        g.assign(n_ + 1, vector<node>());
    }

    inline void add(int u, int v, const T &w) {
        g[u].push_back({v, w});
    }

    inline bool spfa(int beg) {// 返回false,表明有负环
        queue<int> q;
        dist[beg] = 0;
        vis[beg] = true;
        cnt[beg] = 1;
        q.push(beg);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            vis[u] = false;// 出队
            for (auto &[v, w] : g[u]) {
                if (w + dist[u] < dist[v]) {
                    dist[v] = w + dist[u];// 更新最小值
                    if (!vis[v]) {
                        vis[v] = true;// 入队
                        cnt[v]++;
                        if (cnt[v] >= n) {
                            return false;
                        }
                        q.push(v);
                    }
                }
            }
        }
        return true;
    }
};

\(Floyd\) 算法

1、时间复杂度:\(O(|V|^3)\),空间复杂度:\(O(|V|^2)\)

2、利用的是动态规划思想处理的,都支持,只是时间复杂度高。

3、模版:洛谷B3647

4、多源最短路:\(O(|V|^3)\)

template<typename T>
struct Floyd{
    int n;
    vector<vector<T>> dist;
    Floyd() {}
    Floyd(int n_) : dist(n_ + 1, vector<T>(n_ + 1, mof)) {
        n = n_;
        for (int i = 0; i <= n_; i++) {
            dist[i][i] = 0;
        }
    }

    inline void init(int n_) {
        n = n_;
        dist.assign(n_ + 1, vector<T>(n_ + 1, mof));
        for (int i = 0; i <= n_; i++) {
            dist[i][i] = 0;
        }
    }

    inline void add(int u, int v, const T &w) {
        dist[u][v] = min(dist[u][v], w);
    }

    inline void floyd() {
        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }
};

\(Bellman-Ford\) 算法

1、可用于求一个单源图中是否有负环,与 \(dijkstra\) 相比有点在于可以求解负权的最短路,但时间复杂度比 \(dijkstra\) 差。

2、时间复杂度:\(O(|V||E|)\),空间复杂度:\(O(|V|)\)

3、模版:洛谷P3385

4、单源最短路:\(O(|V||E|)\)

template<typename T>
struct Bellman_Ford{
    struct node{
        int u, v;
        T w;
        node(int u = 0, int v = 0, T w = 0) : u(u), v(v), w(w) {}
    };

    int n, tot;
    vector<T> dist;
    vector<node> g;
    vector<int> pre;

    Bellman_Ford() {}
    Bellman_Ford(int n_) : dist(n_ + 1, mof), pre(n_ + 1) {
        n = n_;
        tot = 0;
    }

    inline void add(int u, int v, const T &w) {
        tot++;
        g.push_back({u, v, w});
    }

    inline void init(int n_) {
        n = n_;
        dist.assign(n_ + 1, mof);
        pre.assign(n_ + 1, 0);
    }

    inline bool bellman_ford(int beg) {// 返回false表示有负权环,单源负环
        dist.assign(n + 1, mof);
        dist[beg] = 0;
        bool f = true;
        for (int i = 1; i <= n; i++) {
            bool ok = true;
            for (auto [u, v, w] : g) {
                if (dist[u] == mof) {
                    continue;
                }
                if (dist[u] + w < dist[v]) {
                    dist[v] = dist[u] + w;
                    pre[v] = u;
                    ok = false;
                    if (i == n) {
                        f = false;
                    }
                }
            }
            if (ok) {
                break;
            }
        }
        return f;
    }
};

\(Johnson\) 算法

1、用于求带有负权的多源最短路,在求负权的多源最短路中他是最优的,主要利用的是 \(SPFA\) 算法和 \(Dijkstra\) 算法的结合,利用 \(spfa\) 算法使得图中的负权变为非负,从而可以通过 \(Dijkstra\) 算法来实现多源最短路。

2、时间复杂度:\(|V|^2log|V|+|V||E|\)

3、模版:洛谷P5905

4、注:如果一条边起点为 \(u\),终点为 \(v\),边权值为 \(w\),那么更新后的 \(w=w+h[u]-h[v]\),但是要记得将最后得到的 \(dist[beg][v]=dist[beg][v]+h[v]-h[u]\),这才是最条路径的最短路值,貌似这个算法不是特别重要。

template<typename T>
struct Johnson{
    struct node{
        int v;
        T w;
    };
    struct S{
        T id, dis;
        bool operator< (const S &t) const {
            return dis > t.dis;
        }
    };

    int n;
    vector<T> h;
    vector<int> cnt;
    vector<bool> vis;
    vector<vector<T>> dist;
    vector<vector<node>> g;

    Johnson() {}
    Johnson(int n_) : h(n_ + 1, MOF), cnt(n_ + 1), vis(n_ + 1), dist(n_ + 1, vector<T>(n_ + 1, MOF)), g(n_ + 1) {
        n = n_;
    }

    inline void init(int n_) {
        n = n_;
        h.assign(n_ + 1, MOF);
        cnt.assign(n_ + 1, 0);
        vis.assign(n_ + 1, false);
        g.assign(n_ + 1, vector<node>());
        dist.assign(n_ + 1, vector<T>(n_ + 1, MOF));
    }

    inline void add(int u, int v, const T &w) {
        g[u].push_back({v, w});
    }

    inline bool spfa() {
        for (int i = 1; i <= n; i++) {// 虚拟源点,主要是为了将边权非负化。
            add(0, i, 0);
        }
        h[0] = 0;
        cnt[0]++;
        vis[0] = true;
        queue<int> q;
        q.push(0);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            vis[u] = false;
            for (auto [v, w] : g[u]) {
                if (h[u] + w < h[v]) {
                    h[v] = h[u] + w;
                    if (!vis[v]) {
                        vis[v] = 1;
                        cnt[v]++;
                        q.push(v);
                        if (cnt[v] >= n) {
                            return false;
                        }
                    }
                }
            }
        }
        for (int i = 1; i <= n; i++) {
            for (auto &[v, w] : g[i]) {
                w += h[i] - h[v];
            }
        }
        return true;
    }

    inline void dijkstra(int beg) {// 以beg为源点的最短路
        vis.assign(n + 1, false);
        dist[beg][beg] = 0;
        priority_queue<S> q;
        q.push({beg, 0});
        while (!q.empty()) {
            auto [u, w] = q.top();
            q.pop();
            if (vis[u]) {
                continue;
            }
            vis[u] = true;
            for (auto [v, w] : g[u]) {
                if (dist[beg][u] + w < dist[beg][v]) {
                    dist[beg][v] = dist[beg][u] + w;
                    q.push({v, dist[beg][v]});
                }
            }
        }
    }
};
posted @ 2024-08-27 09:09  grape_king  阅读(41)  评论(0)    收藏  举报