-
单源最短路 n: 点的数量 m: 边的数量
-
所有边权都是正数
-
(1)朴素Dijkstra算法 O(n^2)
-
(2)堆优化版的Dijkstra算法 O(mlogn)
-
-
存在负权边
-
(1)Bellmax-Fold O(nm) (让选择不超过k条边的时候使用)
-
(2)SPFA 一般O(m),最坏O(nm)
-
-
-
多源汇最短路
- Floyd算法 O(n^3)
边权非负
朴素Dijkstra
每次找到距离起点最近的点,然后用这个点去更新其他点,时间复杂度为O(n^2)
Dijkstra求最短路 I
import java.io.*;
import java.util.*;
class Main{
static final int N = 510;
static boolean[] st = new boolean[N];
static int[] dist = new int[N];
static int[][] g = new int[N][N];
static int n, m;
public static void Dijkstra(){
Arrays.fill(dist, 0x3f3f3f3f);
dist[1] = 0;
for (int i = 0; i < n; i++) {
int t = -1;
for(int j = 1; j <= n; j++) {
if (!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
}
st[t] = true;
for (int j = 1; j <= n; j++)
dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] str = br.readLine().split(" ");
n = Integer.parseInt(str[0]);
m = Integer.parseInt(str[1]);
for (int i = 0; i <= n; i++){
Arrays.fill(g[i], 0x3f3f3f3f);
g[i][i] = 0;
}
for (int i = 0; i < m; i++) {
str = br.readLine().split(" ");
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);
g[x][y] = Math.min(g[x][y], z);
}
Dijkstra();
if (dist[n] == 0x3f3f3f3f) System.out.println(-1);
else System.out.println(dist[n]);
}
}
堆优化版Dijkstra
朴素Dijkstra算法,找最近的点需要n次,更新边需要m次,所以总体是O(n^2)
如果用堆优化,找最短的点是O(1),找n次; 更新边是O(logn),更新m次(外层循环是n个点,每次更新的是这个点的连边, 所有点的连边加起来一共m条),时间复杂度为O(mlogn)
不用手写堆,可以用优先队列实现,优先队列不能修改元素,因此需要修改元素就直接插入,因此会有冗余元素存在。手写堆的话,在排序的时候还要交换下标
Dijkstra求最短路 II
import java.io.*;
import java.util.*;
class Main {
static final int N = 150010;
static int[] h = new int[N], e = new int[N], ne = new int[N], w = new int[N];
static int[] dist = new int[N];
static boolean[] st = new boolean[N];
static int n, m, idx;
public static void add(int a, int b, int c) {
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
public static void Dijkstra(){
Arrays.fill(dist, 0x3f3f3f3f);
dist[1] = 0;
PriorityQueue<PII> q = new PriorityQueue<>((o1, o2) -> o1.d - o2.d);
q.offer(new PII(0, 1));
while (!q.isEmpty()){
PII t = q.poll();
int dis = t.d;
int ver = t.u;
if (st[ver]) continue;
st[ver] = true;
// 用点ver更新距离
for (int i = h[ver]; i != -1; i = ne[i]) {
int u = e[i];
if (dist[u] > dis + w[i]) {
dist[u] = dis + w[i];
q.offer(new PII(dist[u], u));
}
}
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] str = br.readLine().split(" ");
n = Integer.parseInt(str[0]);
m = Integer.parseInt(str[1]);
Arrays.fill(h, -1);
for (int i = 0; i < m; i++) {
str = br.readLine().split(" ");
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);
add(x, y, z);
}
Dijkstra();
if (dist[n] == 0x3f3f3f3f) System.out.println(-1);
else System.out.println(dist[n]);
}
}
class PII{
public int d;
public int u;
public PII(int d, int u) {
this.d = d;
this.u = u;
}
}
边权有负值
Bellman-ford
该算法可来求经过k条边的最短路问题,边权可以为负。也可以用来求是否存在负环,但是求负环一般用spfa,Bellman-ford的时间复杂度为O(mn)
算法流程
1、for n 次
; 迭代k次表示:从1号点经过了不超过k条边到其他点的距离;如果第n次更新时,
dist还发生变化(经过了n条边,有n+1个点,则一定有两个点一样),说明存在负权边的环
2、for 所有边 a->b--w dist[b]=min(dist[b],dist[a]+w)
last数组存的是dist上一次更新的备份,假设1->2=1,2->3=1,初始dist[2]=+00,
第一次更新dist[2]=1,更新dist[3]时,就用dist[2]来更新了,所以应该用上一次的结果更新
有边数限制的最短路
import java.io.*;
import java.util.*;
class Main {
static final int N = 510, M = 10010;
static int[] dist = new int[N];
static int[] last = new int[N];
static node[] g = new node[M];
static int n, m, k;
public static void bellman_fold(){
Arrays.fill(dist, 0x3f3f3f3f);
dist[1] = 0;
for (int i = 0; i < k; i++) {
last = Arrays.copyOf(dist, dist.length);
for (int j = 0; j < m; j++) {
int a = g[j].a;
int b = g[j].b;
int c = g[j].c;
if (dist[b] > last[a] + c){
dist[b] = last[a] + c;
}
}
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] str = br.readLine().split(" ");
n = Integer.parseInt(str[0]);
m = Integer.parseInt(str[1]);
k = Integer.parseInt(str[2]);
for (int i = 0; i < m; i++) {
str = br.readLine().split(" ");
int a = Integer.parseInt(str[0]);
int b = Integer.parseInt(str[1]);
int c = Integer.parseInt(str[2]);
g[i] = new node(a, b, c);
}
bellman_fold();
if (dist[n] > 0x3f3f3f3f / 2) System.out.println("impossible");
else System.out.println(dist[n]);
}
}
class node{
int a;
int b;
int c;
public node(int a, int b, int c){
this.a = a;
this.b = b;
this.c = c;
}
}
spfa
spfa是对bellman-Fold进行优化,bellman-Fold每次对所有边进行迭代,
dist[y] = min(dist[y], last[x] + w);
如果last[x]
没有变化,那么dist[y]
也不需要更新
因此每次只需要将变化过的点放入队列,每次从队列取来更新后边的点即可
这里的st数组标记的是该点是否在队列中
spfa时间复杂度一般是O(m), 最坏是O(nm)
spfa求最短路
import java.io.*;
import java.util.*;
class Main{
static final int N = 100010;
static int[] dist = new int[N];
static boolean[] st = new boolean[N];
static int[] h = new int[N], e = new int[N], ne = new int[N], w = new int[N];
static int n, m, idx;
public static void add(int a, int b, int c) {
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
public static void spfa(){
Arrays.fill(dist, 0x3f3f3f3f);
dist[1] = 0;
Queue<Integer> q = new LinkedList<>();
q.offer(1);
st[1] = true;
while (!q.isEmpty()){
int t = q.poll();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int u = e[i];
if (dist[u] > dist[t] + w[i]){
dist[u] = Math.min(dist[u], dist[t] + w[i]);
if (!st[u]) {
q.offer(u);
st[u] = true;
}
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] str = br.readLine().split(" ");
n = Integer.parseInt(str[0]);
m = Integer.parseInt(str[1]);
Arrays.fill(h, -1);
for (int i = 0; i < m; i++) {
str = br.readLine().split(" ");
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);
add(x, y, z);
}
spfa();
if (dist[n] == 0x3f3f3f3f) System.out.println("impossible");
else System.out.println(dist[n]);
}
}
spfa判断负环
维护一个cnt数组即可,cnt[i]表示从1-i经过了多少条边,如果边数大于等于n,说明存在负环
spfa判断负环
import java.io.*;
import java.util.*;
class Main {
static final int N = 2010, M = 10010;
static int[] h = new int[N], e = new int[M], ne = new int[M], w = new int[M], cnt = new int[N];
static int[] dist = new int[N];
static boolean[] st = new boolean[N];
static int n, m, idx;
public static void add(int a, int b, int c) {
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
public static boolean spfa(){
Arrays.fill(dist, 0x3f3f3f3f);
dist[1] = 0;
Queue<Integer> q = new LinkedList<>();
// 因为有的负环可能从点1无法到达,所以将所有点都加入队列
for (int i = 1; i <= n; i++){
q.offer(i);
st[i] = true;
}
while (!q.isEmpty()){
int t = q.poll();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int u = e[i];
if (dist[u] > dist[t] + w[i]) {
dist[u] = dist[t] + w[i];
cnt[u] = cnt[t] + 1;
if (cnt[u] >= n)
return true;
if (!st[u]) {
q.offer(u);
st[u] = true;
}
}
}
}
return false;
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str[] = br.readLine().split(" ");
n = Integer.parseInt(str[0]);
m = Integer.parseInt(str[1]);
Arrays.fill(h, -1);
for (int i = 0; i < m; i++) {
str = br.readLine().split(" ");
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);
add(x, y, z);
}
if (spfa()) System.out.println("Yes");
else System.out.println("No");
}
}
多源汇最短路
Floyd
Floyd实际上是一个dp问题
-
状态表示:
f[k, i, j]
表示从i到j,中间点只经过1到k(不包括i和j)的所有路径最小值 -
状态计算:
f[k, i, j] = min(f[k-1, i, j], f[k-1, i, k] + f[k-1, k, j])
f[k-1, i, j]
表示不经过第k个点,f[k-1, i, k] + f[k-1, k, j]
表示经过第k个点
可以发现f[k, i, j]
只和k-1
层有关系,所以第一维可以去掉
f[i][j] = min(f[i][j], f[i][k] + f[k][j])
f[i][j]
一定是属于第k-1
层的,而f[i][k]
不一定属于k-1
层,如果k小于j,那么f[i][k]
属于第k层,即f[k, i, k] + f[k-1, k, j]
如果有负环,则一定不存在最短路,但是存在最短路,说明从i到j一定存在一条路径不包括重复点且最短.所以f[k,i,k]
表示从i走到k,将1到k作为中间点,如果将k作为中间点,即k到k为一个环,更新最短路时会去点这个负环,所以此时答案不会被影响
Floyd求最短路文章来源:https://www.toymoban.com/news/detail-454074.html
文章来源地址https://www.toymoban.com/news/detail-454074.html
import java.io.*;
import java.util.*;
class Main{
static final int N = 210;
static int[][] dist = new int[N][N];
static int n, m, k;
public static 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] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str[] = br.readLine().split(" ");
n = Integer.parseInt(str[0]);
m = Integer.parseInt(str[1]);
k = Integer.parseInt(str[2]);
for (int i = 1; i <= n; i++){
Arrays.fill(dist[i], 0x3f3f3f3f);
dist[i][i] = 0;
}
for (int i = 0; i < m; i++) {
str = br.readLine().split(" ");
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);
dist[x][y] = Math.min(dist[x][y], z);
}
Floyd();
for (int i = 0; i < k; i++) {
str = br.readLine().split(" ");
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
if (dist[x][y] >= 0x3f3f3f3f / 2) System.out.println("impossible");
else System.out.println(dist[x][y]);
}
}
}
到了这里,关于图论---最短路问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!