首页 > 其他分享 >最短路

最短路

时间:2023-08-22 14:00:18浏览次数:32  
标签:int 短路 算法 MAXN 答案 转移

简介

最短路顾名思义就是求两个点之间的最短距离,在一些题中是非常重要的工具,一般根据不同的情况和限制需使用合适的算法(比如如果有负边权就不能用 \(dijkstra\)),一般分为单元最短路和多源最短路。最经典的算法有 \(dijkstra\),\(floyd\),\(bellman-ford\) 等。

在学习最短路之前,必须了解一些基本概念,本文不在赘述。

Bellman–Ford 算法

简介及基本用途

\(Bellman-ford\) 是很好理解的一种单源最短路算法,可以有负边权,这是它比较有优势的地方,但是它的时间复杂度并不占优,达到了 \(O(nm)\),效率太低,又是单源最短路的算法,在比赛中不是很常见。但是它有一个别的最短路算法都没有的用处,就是可以判负环。闲话不多说已经不少了,开始吧。

小贴士:一般有负边权的图都是单向的,否则只要有负边一定就有负环了。

过程

  • 我们知道两个点之间的简单路径经过的边的数量最多是 \(n - 1\),所以我们可以做 \(n - 1\) 次以下操作(下面的 \(dp_i\) 表示 点 \(i\) 的答案)即可得到答案:

    • 枚举每条边 \([u, v, w]\),执行 \(dp_v = \min(dp_v, dp_u + w)\),也就是通过这条边更新两个点的答案,形式的说就是我们要求起点 \(S -> v\) 的答案,可以通过 \(S->u\) 的答案 \(+ w\) 转移过来。
  • 因为这样一定能把一个点的答案转移到另一个点上去,也就可以求出每个点的答案。

  • 而我们上面说到这个算法可以判负环(前提是整个图联通),现在我们来解释一下:因为如果没有负环,那么进行 \(n - 1\) 次上述操作后,答案一定确定下来了,否则一定会经过重复的点,什么情况才有这种情况呢?那就是有负环的时候,所以我们只用再做一次上面的操作,如果答案更新了,那就有负环了。

  • 其实 Bellan-ford 也是一种 \(DP\),拓扑序就是第一维枚举走了几条边,只是因为我们只用求最短路就省去了这一维。

时间复杂度

这东东复杂度共 \(n - 1\) 次操作,每次操作需枚举每条边进行转移,共 \(O(m)\)。

Code

const int MAXN = 5005, MAXM = 1e4 + 5;

struct Edge {
  int u, v, w;
} a[MAXM];

int n, m, dp[MAXN]; // 记得初始化

void Bellman-ford() {
  dp[1] = 0; // 假设 1 为起点
  for (int i = 1; i < n; i++) {
    for (int j = 1; j <= m; j++) {   // 枚举边进行转移
      dp[a[i].v] = min(dp[a[i].v], dp[a[i].u] + a[i].w);
    }
  }
  bool flag = 1;
  for (int i = 1; i <= m; i++) { // 判负环
    if (dp[a[i].v] > dp[a[i].u]) { // 出现负环
      flag = 0; 
    }
  }
}

Dijkstra 算法

简介及基本用途

\(dijkstra\) 算法是由荷兰计算机科学家 E. W. Dijkstra 于 \(1956\) 年发现,\(1959\) 年公开发表,简称 \(dij\),是一种经典的在非负权图上的单源最短路算法(有点小贪心),边权必须非负!!! 否则这种算法将会出现问题。

过程

  • \(dijkstra\) 的算法思想有点像 \(BFS\),本身 \(BFS\) 就能求最短路,思想就是从小的答案开始一层一层往外扩散,这就是为什么 \(dij\) 的边权必须非负的原因。

  • 但是我们知道 \(BFS\) 的边权是固定的,但是 \(dij\) 就不一定了。每次怎么找到答案最小的进行转移呢?根据不同的方法,时间复杂度也会不同,以下介绍最经典的两种。

    • 第一种就是暴力寻找当前答案最小的那个点进行转移,再进行转移,这是最简单直接的办法。

    • 显然上面的暴力有时候是应付不了的,如果我们要找的答案最小的点,就可以把所有转移到的状态记录下来,从里面找最小的,这个过程一般用优先队列实现。每次转移是把新的状态入队,从里面找出最小的进行转移,循环往复。

  • 另外每个点一定只有从最优状态转移才最有效,也就是其他状态全部无效,所以如果一个点已经被转移过了就无须就行转移了。

正确性证明

简而言之,我们就是要证明我们现在找到的答案最小的点 \(x\),它的答案一定不会比当前更优。其实这个问题很简单,因为我们知道边权是非负的,所以如果有一个点的答案没比当前点答案更有,转移过来也不可能比 \(x\) 的答案更优,而我们知道当前我们找到的是当前最优的状态转移,不可能有比当前状态更有的状态, 所以这个算法是完全成立的。

时间复杂度

暴力

每次寻找最小的点转移,共 \(n\) 次,这里是 \(O(n^2)\),转移 \(O(m)\),共 \(O(n^2)\)。

优先队列

因为一个点可能被多次更新时,而之前的无用状态可能还在优先队列里,不能将其挤出去,所以队列里的元素数量有 \(O(m)\) 个级别,总时间复杂度:\(O(m \log m)\)。

对比

经过对比我们发现在一些稠密图情况下,\(m = n^2\),暴力反而更有优势。令外 Dijkstar 对比 Bellman–Ford 时间复杂度占优势,但是 Bellman–Ford 的优势在于可以有负边权,还可以判负环。

下面挂一下伪代码

暴力 Code

struct Edge {
  int x, w;
};

bool v[MAXN] = {0, 1};
int n, m, d[MAXN] = {INF}; // 需初始化
vector<Edge> g[MAXN];

void dij() { // 假设起点是 1
  for (int i = 2, x = 1; i <= n; i++) {
    int y = 0;
    for (auto e : g[x]) { // 转移
      d[e.x] = min(d[e.x], d[x] + e.w);
    }
    for (int i = 1; i <= n; i++) { // 求当前的最小答案
      if (!v[i] && d[i] < d[y]) {
        y = i;
      }
    }
    x = y, v[x] = 1;
  }
}

优先队列 Code

struct Edge { // 边
  int x, w;
};

struct Node { // 状态
  int x, w;  // 点的编号和距离
};

struct cmp {
  bool operator ()(const Node &i, const Node &j) {
    return i.w > j.w;
  }
};

bool v[MAXN];
int n, m, d[MAXN]; // 需初始化最大值
vector<Edge> g[MAXN];
priority_queue<Node, vector<Node>, cmp> pq; // 优先队列

void Record(int x, int w) {
  if (d[x] > w) { // 当前转移比当前点答案更有
    d[x] = w;  // 更新答案
    pq.push({x, w}); // 入队
  }
}

void dij() { // 假设起点是 1
  for (Record(1, 0); !pq.empty(); ) {
    Node u = pq.top(); pq.pop();
    if (v[u.x]) continue; // 已经转移过了
    for (auto e : g[u.x]) { // 转移
      Record(e.x, u.w + e.w);
    }
  }
}

未完待续。。。

标签:int,短路,算法,MAXN,答案,转移
From: https://www.cnblogs.com/xhr0817-blog/p/17644446.html

相关文章

  • 用 Dijkstra 算法解决最短路问题
    话不多说,先看图1.1朴素版的Dijkstra算法一般用到这个情况稠密图,也就是节点的个数比边的个数少。(稠密图用邻接矩阵存储)#include<cstring>#include<iostream>#include<algorithm>usingnamespacestd;constintN=510;intn,m;intg[N][N];//稠密图用邻接矩阵,g......
  • 为什么会变成这样呢? #5(常数边权最短路)
    给定一个\(n\)个点\(m\)条边的有边权无向图,其中边权\(w_i\in\{0,1,\dots,k-1\}\),求点\(1\)到各个点的最短路。期望复杂度:\(O((n+m)k)\)0k最短路在经典的Dijkstra算法中,我们使用一个优先队列来维护松弛队列,这样的时间复杂度为\(O((n+m)\logk)\)。现在我们考虑为每种......
  • 最短路总结
    最短路径目录最短路径\(\operatorname{Floyd}\)(全源最短路)\(\operatorname{Dijkstra}\)(非负权图单源最短路)\(\operatorname{Bellman-Ford}\)(带负权单源最短路)\(\operatorname{Johnson}\)(全源最短路)总结参考文献:\(\operatorname{Floyd}\)(全源最短路)我们定义一个数组\(f_{k,x,y......
  • 单源最短路问题
    Bellman-Ford算法例题【模板】负环原理Bellman-Ford算法的原理是重复遍历\(n-1\)遍所有的边,对其进行松弛操作。如果源点到终点的距离小于源点到起点与这条边的权值之和,那么源点到终点的距离就用这个更小的距离来替换。核心代码:if(dis[e[j].from]+e[j].value<......
  • 多源最短路问题
    Floyd算法例题【模板】Floyd算法原理Floyd算法的思想是动态规划。维护一个数组dis[k][u][v],表示从点\(u\)到点\(v\)的最短路径,且中间经过的点(不包括\(u\),\(v\))的序号最大为\(k\)。状态转移方程:\(f(k,u,v)=min{(f(k-1,u,v),f(k-1,u,k)+f(k-1,k,v))}\)为......
  • AcWing 854. Floyd求最短路
    题目给定一个$n$个点$m$条边的有向图,图中可能存在重边和自环,边权可能为负数。再给定$k$个询问,每个询问包含两个整数$x$和$y$,表示查询从点$x$到点$y$的最短距离,如果路径不存在,则输出impossible。数据保证图中不存在负权回路。输入格式第一行包含三个整数$n,m,k......
  • 最短路&差分约束笔记
    最短路径基础算法单源最短路径单元最短路径指的是在一张联通图中,起点\(s\)到其他所有点的最短路径。计算单元最短路的常见算法有:\(spfa\),\(dijkstra\)。若图带负边权(注意,此时只能是有向图,无向图负边权类似负环),则必须使用\(spfa\),时间复杂度\(O(kE)\),\(E\)表示边的数量;最......
  • 浅谈关于电气线路短路引起的电气火灾的原因
    未晓妃安科瑞电气股份有限公司上海嘉定201801摘要:我国人口众多,同时对用电需求也非常多。我们日常生活需要电力来照亮我们的家园并为工业供电。它可以是建设性的,也可以是破坏性的,这取决于处理它的谨慎程度。稍有不慎,就会引发火灾,造成经济损失和惨痛的伤害。电气火灾中*危险的故障......
  • AcWing 851. spfa求最短路
    题目给定一个$n$个点$m$条边的有向图,图中可能存在重边和自环,边权可能为负数。请你求出$1$号点到$n$号点的最短距离,如果无法从$1$号点走到$n$号点,则输出impossible。数据保证不存在负权回路。输入格式第一行包含整数$n$和$m$。接下来$m$行每行包含三个整......
  • 考研数据结构——每日一题[Dijkstra求最短路]
    849.Dijkstra求最短路I给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值。请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出−1。输入格式第一行包含整数n和m。接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点......