最短路模板

最短路

Floyed

时间复杂度:\(O(n^3)\)

标准弗洛伊德算法,三重循环。循环结束之后 \(dis[i][j]\) 存储的就是点 \(i\) 到点 \(j\) 的最短距离。

需要注意循环顺序不能变:第一层枚举中间点,第二层和第三层枚举起点和终点。

题目多数时间限制为 \(1s\),因此一般适合 \(n\le 300\) 的情况。

#include <bits/stdc++.h>
using namespace std;

const int N = 310, INF = 0x3f3f3f3f;

int n, m;
int dis[N][N];	//存储两点之间的最短距离

int main() {
    memset(dis, 0x3f, sizeof(dis));
    cin >> m >> n;
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        dis[a][b] = dis[b][a] = min(c, dis[a][b]);
    }
    // floyd 算法核心
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
    cout << dis[1][n] << endl;
    return 0;
}

Dijkstra

朴素的 Dijkstra

时间复杂度:\(O(n^2)\)

朴素的 Dijkstra 算法,图用邻接矩阵存储,基于贪心的思想,每次暴力循环找距离起点最近的,且未被标记过的点进行松弛操作。

不能求最长路,不能求有负边权的图的最短路。

#include <bits/stdc++.h>
using namespace std;

const int N = 310, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dis[N]; // 邻接矩阵, dis[i]表示起点到 i 的最短距离
bool vis[N];         // vis[i] 标记结点 i 的最短距离是否已确定

void dijkstra(int s) { // 求 s 到其他结点的最短距离
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    // 循环 n 次,每次确定一个结点的最短距离并标记
    for (int i = 0; i < n; i++) {
        int k = -1, mind = INF;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && dis[j] < mind) {
                mind = dis[j];
                k = j;
            }
        }
		if (k == -1) break; // 图不连通,终止
        vis[k] = true;
        for (int j = 1; j <= n; j++) {
			if (!vis[j] && dis[j] > dis[k] + g[k][j])
			dis[j] = dis[k] + g[k][j];
		}
    }
}

int main()
{
    cin >> m >> n;
`
    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c); // 去重边
    }
    dijkstra();
    cout << dis[n] << endl;
    return 0;
}

带权有向图 Dijkstra + 堆优化 + vector 存图

用小根堆维护起点到所有点的距离。时间复杂度是 \(O(mlogn)\)

直接使用 STL 中的 priority_queue,将所有修改过的点直接插入堆中,堆中会有重复元素,但堆中元素总数不会大于 \(m\)

struct Node {
    int id, dis;
    bool operator < (const Node &A) const {
        return dis > A.dis;
    }
};
int dis[N];
bool vis[N];
void dij(int s) {
    priority_queue<Node> q;
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, false, sizeof(vis));
    dis[s] = 0;
    vis[s] = true;
    q.emplace(Node{s, 0});
    Node nd;
    while (!q.empty()) {
        nd = q.top();
        q.pop();
        s = nd.id;
        if (vis[s]) continue;
        vis[s] = true;
        for (const auto &e : edge[s]) {
            if (!vis[e.to] && dis[e.to] > dis[s] + e.w) {
                dis[e.to] = dis[s] + e.w;
                q.emplace(Node{e.to, dis[e.to]});
            }
        }
    }
}

SPFA

对 Bellman-ford 算法的优化版本,可以处理存在负边权的最短路问题。

最坏情况下的时间复杂度是 \(O(nm)\),但对于随机图,SPFA 算法的运行效率非常高,期望运行时间是 \(O(km)\),其中 \(k\) 是常数。

但需要注意的是,在网格图中,SPFA 算法的效率比较低,如果边权为正,则尽量使用 Dijkstra 算法。

邻接矩阵存图+SPFA

const int N = 1010;
const int INF = 0x3f3f3f3f;

int g[N][N], dis[N]; //邻接矩阵存图
int t, n, m, whole;

void SPFA(int s) {
    bool vis[N] = {};
    queue<int> que;
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));
    //起点初始化,入队并标记
    dis[s] = 0;
    vis[s] = true;
    que.push(s);
    while (!que.empty()) {
        s = que.front(); que.pop();
        vis[s] = 0; //标记出队

        for (int i = 1; i <= n; i++) {
            if (g[s][i] < INF && dis[s] + g[s][i] < dis[i]) {
                dis[i] = dis[s] + g[s][i];
                if (!vis[i]) { //不在队列中,则加入
                    que.push(i);
                    vis[i] = 1;
                }
            }
        }
    }
}

邻接矩阵存图+SPFA判负环

用小根堆维护起点到所有点的距离。时间复杂度是 \(O(mlogn)\)

直接使用 STL 中的 priority_queue,将所有修改过的点直接插入堆中,堆中会有重复元素,但堆中元素总数不会大于 \(m\)

const int N = 1010;
const int INF = 0x3f3f3f3f;

int g[N][N], dis[N]; //邻接矩阵存图
int cnt[N]; //cnt记录每个结点入队次数
int n, m;

bool SPFA(int s) { //bfs版spfa判负环
    bool vis[N] = {};
    queue<int> que;
    memset(vis, 0, sizeof(vis));
    memset(cnt, 0, sizeof(cnt));
    memset(dis, 0x3f, sizeof(dis));

    dis[s] = 0;
    vis[s] = true;
    que.push(s);
    cnt[s]++; //起点入队次数加1
    while (!que.empty()) {
        s = que.front(); que.pop();
        vis[s] = 0; //标记出队

        for (int i = 1; i <= n; i++) {
            if (g[s][i] < INF && dis[s] + g[s][i] < dis[i]) {
                dis[i] = dis[s] + g[s][i];
                if (!vis[i]) {
                    que.push(i);
                    cnt[i]++; //入队数次加1
                    if (cnt[i] >= n) //不存在负环的话,每个结点最多入队n-1次
                        return true; //存在负环
                    vis[i] = 1;
                }
            }
        }
    }
    return false; //不存在负环
}
posted @ 2024-01-26 17:27  狂飙霹雳虎  阅读(56)  评论(0编辑  收藏  举报