首页 > 其他分享 >最短路

最短路

时间:2023-10-09 09:55:04浏览次数:34  
标签:松弛 int 短路 负环 节点 dis

前言

定义

  • 从某一点出发到某一点的最短路

性质

  • 对于边长为正的图:
    • 任意两个节点之间的最短路,不会经过重复的节点。
    • 任意两个节点之间的最短路,不会经过重复的边。
    • 任意两个节点之间的最短路,任意一条的节点数不会超过 \(n\),边数不会超过 \(n-1\)。

记号

  • \(n\) 为图上点的数目,\(m\) 为图上边的数目。
  • \(s\) 为最短路的出发点。
  • \(D(u)\) 为 \(s\) 点到 \(u\) 点的实际最短路长度。
  • \(dis(u)\) 为 \(s\) 点到 \(u\) 的 估计 最短路长度。任何时候有 \(dis(u) \geqslant d(u)\) 。

正文

\(Floyd\) 算法

是用来实现任意两节点之间的最短路的。

复杂度: \(o(n^3)\)

适用于任何图,但不能存在负环

  • 实现:

示例图:

image

我们定义一个数组 \(f[k][x][y]\),表示只允许经过节点 \(1 \to k\) 中,节点 \(x\) 到节点 \(y\) 的路径。

那么 \(f[n][x][y]\) 就是节点 \(x\) 到节点 \(y\) 的最短路长度(此时 \(n\) 个点的子图 \(V'\) 即为图 \(V\) 本身)

特殊的点

\(f[0][x][y]\):从 \(x \to y\) 的边权,或为 \(0\),或为 \(+\infty\) 。

  • \(x\) 与 \(y\) 直接相连:\(x \to y\) 的边权 \(w\)
  • \(x\) 与 \(y\) 没有直接相连的边:\(+\infty\)
  • \(x = y\) :\(0\) 。

主要求和式:

f[k][x][y] = f[k-1][x][y] , f[k-1][x][k] + f[k-1][k][y];

( \(f[k - 1][x][y]\) 为不经过 \(k\) 点的最短路径,\(f[k-1][x][k] + f[k - 1][k][y]\) 为经过 \(k\) 点的最短路径)

需要枚举任意两点的最短路的话,空间和时间复杂度都是 \(o(n^3)\) 。

实现代码如下:

for (k = 1; k <= n; k++) {
  for (x = 1; x <= n; x++) {
    for (y = 1; y <= n; y++) {
      f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]);
    }
  }
}
优化:空间复杂度:\(o(n^3) \to o(n^2)\)

第一维度对结果无影响,即:

f[k][x][y] = f[k-1][x][y] , f[k-1][x][k] + f[k-1][k][y];

可变为:

f[x][y] = min(f[x][y],f[x][k] + f[k][y]);

实现代码:

for (k = 1; k <= n; k++) {
  for (x = 1; x <= n; x++) {
    for (y = 1; y <= n; y++) {
      f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
    }
  }
}

\(Bellman ford\)算法

\(Bellman–Ford\) 算法是一种基于松弛( \(relax\) )操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进行判断。

过程

松弛操作:

对于边 \((u , v)\),对应下面的松弛操作:\(dis(v)=min(dis(v),dis(u)+w(u,v))\) 。

\(bellman ford\) 算法所做的就是对图上的每一条边进行松弛操作,且当一轮循环中没有成功的松弛操作时,算法停止。

时间复杂度:\(o(nm)\)

特殊情况:如果从 \(S\) 点出发,抵达一个负环时,松弛操作会无休止地进行下去。对于最短路存在的图,松弛操作最多只会执行 \(n-1\) 轮,因此如果第 \(n\) 轮循环时仍然存在能松弛的边,说明从 \(S\) 点出发,能够抵达一个负环

负环判断的\(tip\)

需要注意的是,以 S 点为源点跑 Bellman–Ford 算法时,如果没有给出存在负环的结果,只能说明从 S 点出发不能抵达一个负环,而不能说明图上不存在负环。

因此如果需要判断整个图上是否存在负环,最严谨的做法是建立一个超级源点,向图上每个节点连一条权值为 0 的边,然后以超级源点为起点执行 Bellman–Ford 算法。

实现代码:

struct edge {
  int v, w;
};

vector<edge> e[maxn];
int dis[maxn];
const int inf = 0x3f3f3f3f;

bool bellmanford(int n, int s) {
  memset(dis, 63, sizeof(dis));
  dis[s] = 0;
  bool flag;  // 判断一轮循环过程中是否发生松弛操作
  for (int i = 1; i <= n; i++) {
    flag = false;
    for (int u = 1; u <= n; u++) {
      if (dis[u] == inf) continue;
      // 无穷大与常数加减仍然为无穷大
      // 因此最短路长度为 inf 的点引出的边不可能发生松弛操作
      for (auto ed : e[u]) {
        int v = ed.v, w = ed.w;
        if (dis[v] > dis[u] + w) {
          dis[v] = dis[u] + w;
          flag = true;
        }
      }
    }
    // 没有可以松弛的边时就停止算法
    if (!flag) break;
  }
  // 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
  return flag;
}

列队优化:\(SPFA\)

只有上一次进行过松弛操作,所连接的边,才可能引起下一次的松弛操作。

可以用列队来维护可能引起松弛操作的点。

解释:

image

如上图,点 \(1\) 为 \(S\) 点,显然只有 \(5\) 和 \(2\) 两点进行了松弛操作,只要用列队存储这两个点,就可以只访问必要的边了。

示例代码:

struct edge {
  int v, w;
};

vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;

bool spfa(int n, int s) {
  memset(dis, 63, sizeof(dis));
  dis[s] = 0, vis[s] = 1;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop(), vis[u] = 0;
    for (auto ed : e[u]) {
      int v = ed.v, w = ed.w;
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数
        if (cnt[v] >= n) return false;
        // 在不经过负环的情况下,最短路至多经过 n - 1 条边
        // 因此如果经过了多于 n 条边,一定说明经过了负环
        if (!vis[v]) q.push(v), vis[v] = 1;
      }
    }
  }
  return true;
}

\(Dijkstra\) 算法

是一种求解非负权图的单源最短路径的算法

过程

将节点分为两个集合:已确定最短路长度的集合 \((\) 记作 \(S\) 集合 \()\),和没有确定最短路长度的点集 \((\) 记作 \(T\) 集合 \()\) 。
一开始所有的点属于 \(T\) 集合。

初始化 \(dis(s) = 0\),其他点的 \(dis\) 均为 \(+\infty\)。

然后重复这些操作:

  1. 从 \(T\) 集合中,选取一个最短路长度最小的结点,移到 \(S\) 集合中。
  2. 对那些刚刚被加入 \(S\) 集合的结点的所有出边进行松弛操作 \((Relax)\)。

直到 \(T\) 集合为 \(empty\)

标签:松弛,int,短路,负环,节点,dis
From: https://www.cnblogs.com/wyl123ly/p/17750799.html

相关文章

  • 最短路
    OI-wikiLink最短路问题,顾名思义就是要求图上的某点到另外一点的最短距离,爆搜就不用说了。令图上点数为\(n\),边数为\(m\)。由于考虑的是最短路,那么包含负环的图就不能算了,没有最短这一说。正权图最短路性质:任意两点间的最短路都不会经过重复的点和重复的边。$$\texttt{Floy......
  • 使用最短路径算法检查项目循环依赖
    最近项目组让我做一个自研的小工具,用来检查代码里的循环依赖,这里做下记录。思路由于工作是网络算路的,第一个想法就是通过路径计算来实现这个功能:把项目里test,resource等文件夹排除,剩下的每一个java文件可以算是对应一个类,把每个类看做是网络/路网里的节点,把类与类之间的依赖关......
  • 最短路径问题 java实现 源代码
    最短路径问题 java实现源代码下载地址:用到的资源文件 文件名 shortPath.propertiesbegin=/u59CB/u53D1/u5730/uFF1Aclear=/u6E05/u9664clearString=/u6E05/u695A/u7ED8/u56FE/u533A/u548C/u6240/u6709/u7684/u6587/u672CdrawLine=/u7ED8/u5236/u8DEF/u5F84end=/u76EE/......
  • 动态规划——DP与最短路 学习笔记
    动态规划——DP与最短路学习笔记例题:P2761软件补丁问题,很容易写出转移方程:\(dp_s\leftarrowdp_{s\setminusF_1\cupF_2}+t_i\),但是这样就出现了环,没有形成DAG就无法跑动态规划了,怎么办?可以将原问题转换为[最短路]:将原状态\(s\)记为一个点,将原转移路径记为一条边\(......
  • 同余最短路
    prologue都快csp-s了还啥也不会的废柴一根,真不知道能不能进队(痴人说梦)。mainbody同余最短路的适用题型当出现形如「给定n个整数,求这n个整数能拼凑出多少的其他整数(n个整数可以重复取)」,以及「给定n个整数,求这n个整数不能拼凑出的最小(最大)的整数」,或者「至少要拼几......
  • Johnson 全源最短路
    Johnson全源最短路Johnson和Floyd一样是能求出无负环图上任意两点间最短路径的算法。引入求任意两点间的最短路可以通过枚举起点,跑\(n\)次SPFA来解决,时间复杂度是\(O(n^2m)\)的,也可以用Floyd解决,复杂度为\(O(n^3)\)。或者我们可以跑\(n\)次堆优化的Dijkstra,......
  • P1144 最短路计数 题解
    Problem考察算法:拓扑排序+\(DP\)+\(Dijkstra\)。题目简述给出一个无向无权图,问从顶点\(1\)开始,到其他每个点的最短路有几条。思路先求出\(1\)号点到每个点的最短路\(d_i\)。分析每条边$(x,y)$:如果d[x]+1==d[y]:这条边有用。将所有有用的边拓扑排序......
  • 853. 有边数限制的最短路
    第一版err#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<cmath>#defineN505usingnamespacestd;intn,m,k,dis[N],cnt,hd[N],vis[N],x,y,z;structEdge{intto,nxt......
  • 单源最短路模板
    SPFA#include<bits/stdc++.h>#definerintregisterint#defineendl'\n'usingnamespacestd;constintN=1e5+5;constintM=1e6+5;constintinf=1e9;inth[N],e[M],ne[M],dist[N],w[M];intn,m,s,idx;queue<int>......
  • [算法分析与设计] 1. 全源最短路近似
    全源最短路(APSP)近似。有两种近似stretch\(k\).\(\delta(u,v)\leqd(u,v)\leqk\cdot\delta(u,v)\).surplus\(t\).\(\delta(u,v)\leqd(u,v)\leq\delta(u,v)+t\).其中,\(\delta(u,v)\)表示\(u,v\)间真实的最短路长度。先来考虑无权图上的surplus......