单源最短路算法

Dijkstra

算法原理

首先将所有点分为两类,确定和没确定最短路的点。

首先我们可以知道,起点只能通过已经确定最短路的点去到其他点,所以我们可以不断的确定距离起点最近的点的路径长度,再用这个确定的点更新其他点到起点的最短路距离。

此时找最近点为 \(O(n)\),更新邻接点为 \(O(m)\),每次因为确定一个点,所以时间复杂度为 \(O(n^2+m)\)

我们发现找距离最近的点其实是一个动态找最值的过程,考虑堆优化,用堆维护点,时间复杂度:\(O((n+m)\log n)\)

证明

假定当前未确定最短路的点中,离起点最近的点为 \(y\),与已确定最短路的点 \(x\) 之间有直接连边,那么这时如果可以更新起点到 \(y\) 最短路的话,那么就可以直接更新。

用反证法证明,如果起点到 \(x\) 的最短路经过一个没确定最短路的点 \(b\) 且点 \(b\) 与已经确定最短路的点 \(a\) 有直接连边,由于起点到点 \(a\) 的长度不小于到点 \(x\) 的长度,并且 \(b\)\(y\) 的距离非负,所以这个路径并不会更短。

注意

  • Dijkstra 不能处理负权图。

也很容易举出反例:

在上面这张图里如果以点 \(2\) 为起点,那么会先更新点 \(1\),但是考虑到点 \(3\) 可能会更新点 \(1\),且到点 \(1\) 的最短路径为 \(2\to 3\to 1\),所以Dijkstra 不能处理负权图。

同理 Dijkstra 也不能处理最长路。

P4779 【模板】单源最短路径(标准版)代码
#include <bits/stdc++.h>

using namespace std;
using PII = pair <int, int>;

const int N = 1e5 + 5;
struct V {
  vector <PII> e;
  int d = INT_MAX;
} v[N];
priority_queue <PII, vector <PII>, greater <PII> > q;
int n, m, s; 

void Dijkstra() {
  for (q.push({0, s}); q.size();) {
    PII p = q.top(); 
    q.pop()
    if (p.first < v[p.second].d) {
      v[p.second].d = p.first;
      for (PII e : v[p.second].e) {
        q.push({p.first + e.second, e.first});
      }
    }
  }
}

int main() {
  cin >> n >> m >> s;
  for (int i = 1, x, y, w; i <= m; i ++) {
    cin >> x >> y >> w;
    v[x].e.push_back({y, w});
  }
  Dijkstra();
  for (int i = 1; i <= n; i ++) {
    cout << v[i].d << " ";
  }
}

bellman_ford

在最短路中,一条边至多被松弛 \(n-1\) 次,所以我们枚举 \(n-1\) 轮,在枚举每条边判断边的两端是否可以松弛,其余跟 Dijkstra 差不多。

时间复杂度 \(O(NM)\)

P3371 【模板】单源最短路径(弱化版 70 分)代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 5e5 + 5;
int n, m, s, dis[N];
vector<tuple<int, int, int>> e;

signed main() {
  cin >> n >> m >> s;
  for (int i = 1, u, v, w; i <= m; i++) {
    cin >> u >> v >> w, e.push_back({u, v, w});
  }
  fill(dis + 1, dis + n + 1, 2147483647), dis[s] = 0;
  for (int T = 1; T < n; T++) {
    for (auto [u, v, w] : e) {
      dis[v] = min(dis[v], dis[u] + w);
    }
  }
  for (int i = 1; i <= n; i++) {
    cout << dis[i] << ' ';
  }
}

SPFA

bellman_ford 的队列优化版本。

只有松弛过的点才能松弛其他点,用队列记录每一个被松弛的点,每次取出队头,松弛他的邻接点,如果松弛成功就压入队列,如果一个点已经在队列里了,那么就不用再进队。

注意每次取出队头时,标记取消。

P3371 【模板】单源最短路径(弱化版)

#include <bits/stdc++.h>
#define int long long

using namespace std;
using PII = pair<int, int>;

const int N = 5e5 + 5;
int n, m, s, h = 1, t, q[N * 10];
struct V {
  vector<PII> e;
  int d = INT_MAX, fl = false;
} v[N];

void Record(int x, int d) {
  if (v[x].d > d) {
    v[x].d = d;
    !v[x].fl && (q[++t] = x, v[x].fl = 1);
  }
}

signed main() {
  cin >> n >> m >> s;
  for (int i = 1, x, y, w; i <= m; i++) {
    cin >> x >> y >> w;
    v[x].e.push_back({y, w});
  }
  for (Record(s, 0); h <= t; h++) {
    v[q[h]].fl = 0;
    for (auto [nxt, w] : v[q[h]].e) {
      Record(nxt, v[q[h]].d + w);
    }
  }
  for (int i = 1; i <= n; i++) {
    cout << v[i].d << " ";
  }
}

作者:Livedreamyhy

出处:https://www.cnblogs.com/Livedreamyhy/p/18191158

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Livedremyhy  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示