最短路

221 阅读12分钟

最短路

Snipaste_2023-03-14_18-14-38.png

朴素dijkstra

  • 适用于稠密图(m~n^2^)
  • 时间复杂度O(n^2^+m),n表示点数,m表示边数

Snipaste_2023-03-14_18-39-32.png

  • C++
int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
  • Java
public static int[][] g = new int[N][N];   //邻接矩阵存储每条边
public static int[] dist = new int[N];     //存储1号点到每个点的最短距离
public static boolean st = new boolean[N]; //存储每个点的最短路是否已经确定

public static int dijkstra() {
    //初始化dist数组
    Arrays.fill(dist, 0x3f3f3f3f);
    dist[1] = 0;
    
    //对于n个点只用找n - 1次
    for (int i = 0; i < n - 1; i++) {
        //在还未确定最短路的点中,寻找距离最小的点
        int t = -1;
        for (int j = 1; j <= n; j++) {
            if (!st[j] && (t == -1 || dist[t] > dist[j])) {
                t = j;
            }
        }
        st[t] = true;
        
        //用t更新其他点的距离
        for (int j = 1; j <= n; j++) {
            dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
        }
    }
    
    if (dist[n] == 0x3f3f3f3f) {
        return -1;
    }
    return dist[n];
}

01 Dijkstra求最短路 I

  • 题目

Snipaste_2023-03-14_19-30-28.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 510;
    public static int[][] g = new int[N][N];
    public static int[] dist = new int[N];
    public static boolean[] st = new boolean[N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        //初始化图
        for (int i = 0; i < n; i++) {
            Arrays.fill(g[i], 0x3f3f3f3f);
        }
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a, b, c);
        }

        pw.println(dijkstra());
        br.close();
        pw.close();
    }

    public static void add(int a, int b, int c) {
        //选最小权加入数组
        g[a][b] = Math.min(g[a][b], c);
    }

    public static int dijkstra() {
        //初始化距离数组
        Arrays.fill(dist, 0x3f3f3f3f);
        dist[1] = 0;

        for (int i = 0; i < n - 1; i++) {
            //在还为确定最短路的点中,寻找距离最小的点
            int t = -1;
            for (int j = 1; j <= n; j++) {
                if (!st[j] && (t == -1 || dist[t] > dist[j])) {
                    t = j;
                }
            }
            //用这个寻找到的点更新其他点的距离
            for (int j = 1; j <= n; j++) {
                dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
            }
            //记得更新布尔数组
            st[t] = true;
        }

        if (dist[n] == 0x3f3f3f3f) {
            return -1;
        }
        return dist[n];
    }
}

堆优化版dijkstra

  • 适用于稀疏图(m~n)
  • 时间复杂度O(mlogn),n表示点数,m表示边数
    • 如果是手写堆,可以保证堆中始终只用n个点,所以时间复杂度就是O(mlogn)
    • 但如果直接用优先队列实现堆,由于不支持修改一个元素的操作,所以每次修改只能往堆里插入一个新的数,堆中会存在冗余,到最后堆里的元素个数可能有m个,此时时间复杂度就变成了O(mlogm)

Snipaste_2023-03-14_19-40-41.png

  • C++
typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
  • Java
public static final N = 100010;
public static final M = N * 2;
public static int[] h = new int[N];
public static int[] w = new int[N];  //存权重
public static int[] e = new int[N];
public static int[] ne = new int[N];
public static int dist = new int[N];  //存每个点的最短距离是否已确定
public static boolean[] st = new boolean[N];
public static n, idx;

public static class Pair implements Comparable<Pair> {
    public int first;  //存距离
    public int second; //存编号

    public Pair(int first, int second) {
        this.first = first;
        this.second = second;
    }

    //自定义类作为堆的泛型时记得实现Comparable接口
    @Override
    public int compareTo(Pair o) {
        return this.first - o.first;
    }
}

public static int dijkstra() {
    //初始化dist数组
    Arrays.fill(dist, 0x3f3f3f3f);
    dist[1] = 0;
    //初始化堆
    PriorityQueue<Pair> heap = new PriorityQueue<>();
    heap.offer(new Pair(0, 1));
    
    //只要堆中没有点了,就代表所有点的最短路都找到了
    while (heap.size() != 0) {
        //从堆顶弹出一个点
        Pair t = heap.poll();
        int ver = t.second;
        int distance = t.first;
        
        //如果当前点已经确定了最短路,直接continue
        if (st[ver]) {
            continue;
        }
        st[ver] = true;
        
        //用这个点更新到其他点的最短距离
        for (int i = h[ver]; i != -1; i = ne[i]) {
            int j = e[i];
            //如果成功更新,添加进堆
            if (dist[j] > distance + w[i]) {
                dist[j] = distance + w[i];
                heap.offer(new Pair(dist[j], j));
            }
        }
    }
    
    if (dist[n] == 0x3f3f3f3f) {
        return -1;
    }
    return dist[n];
}

02 Dijkstra求最短路 II

  • 题目

Snipaste_2023-03-14_22-30-41.png

  • 题解1
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 150010;
    public static int[] h = new int[N];
    public static int[] e = new int[N];
    public static int[] ne = new int[N];
    public static int[] w = new int[N];  //存权重
    public static int[] dist = new int[N];  //存最短距离
    public static boolean[] st = new boolean[N];  //存最短距离是否已确定
    public static int idx, n, m;

    public static class Pair implements Comparable<Pair> {
        public int first;
        public int second;

        public Pair(int first, int second) {
            this.first = first;
            this.second = second;
        }

        //自定义类作为堆的泛型时记得实现Comparable接口
        @Override
        public int compareTo(Pair o) {
            return this.first - o.first;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        //初始化邻接表
        Arrays.fill(h, -1);
        idx = 0;
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a, b, c);
        }

        pw.println(dijkstra());
        br.close();
        pw.close();
    }

    public static void add(int a, int b, int c) {
        w[idx] = c;
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }

    public static int dijkstra() {
        //初始化dist数组
        Arrays.fill(dist, 0x3f3f3f3f);
        dist[1] = 0;
        //初始化堆
        PriorityQueue<Pair> heap = new PriorityQueue<>();
        heap.offer(new Pair(0, 1));

        //只要堆中没有点了,就代表所有点的最短路都找到了
        while (heap.size() != 0) {
            //从堆顶弹出一个点
            Pair t = heap.poll();
            int ver = t.second;
            int distance = t.first;

            //如果当前点已经确定了最短路,直接continue;
            if (st[ver]) {
                continue;
            }
            st[ver] = true;

            //用这个点更新到其他点的最短路径
            for (int i = h[ver]; i != -1; i = ne[i]) {
                int j = e[i];
                //如果成功更新,添加进堆
                if (dist[j] > distance + w[i]) {
                    dist[j] = distance + w[i];
                    heap.offer(new Pair(dist[j], j));
                }
            }
        }

        if (dist[n] == 0x3f3f3f3f) {
            return -1;
        }
        return dist[n];
    }
}
  • 题解二
    • 使用数组实现Pair,很巧妙的方式,但是慢于自定义Pair类,可以学习一下
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 150010;
    public static int[] h = new int[N];
    public static int[] e = new int[N];
    public static int[] ne = new int[N];
    public static int[] w = new int[N];  //存权重
    public static int[] dist = new int[N];  //存最短距离
    public static boolean[] st = new boolean[N];  //存最短距离是否已确定
    public static int idx, n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        //初始化邻接表
        Arrays.fill(h, -1);
        idx = 0;
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a, b, c);
        }

        pw.println(dijkstra());
        br.close();
        pw.close();
    }

    public static void add(int a, int b, int c) {
        w[idx] = c;
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }

    public static int dijkstra() {
        //初始化dist数组
        Arrays.fill(dist, 0x3f3f3f3f);
        dist[1] = 0;
        //初始化堆
        PriorityQueue<int[]> heap = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]);
        heap.offer(new int[]{0, 1});

        //只要堆中没有点了,就代表所有点的最短路都找到了
        while (!heap.isEmpty()) {
            //从堆顶弹出一个点
            int[] t = heap.poll();
            int ver = t[1];
            int distance = t[0];

            //如果当前点已经确定了最短路,直接continue;
            if (st[ver]) {
                continue;
            }
            st[ver] = true;

            //用这个点更新到其他点的最短路径
            for (int i = h[ver]; i != -1; i = ne[i]) {
                int j = e[i];
                //如果成功更新,添加进堆
                if (dist[j] > distance + w[i]) {
                    dist[j] = distance + w[i];
                    heap.offer(new int[]{dist[j], j});
                }
            }
        }

        return dist[n] == 0x3f3f3f3f ? -1 : dist[n];
    }
}

bellman-ford

  • 时间复杂度O(nm),n表示点数,m表示边数
  • 适用于有负环的图,多用于求有边数限制的最短路
  • 没有边数限制的话不如spfa

Snipaste_2023-03-15_18-32-35.png

  • C++
//注意在模板题中需要对下面的模板稍作修改,加上备份数组,详情见模板题
int n, m;       // n表示点数,m表示边数
int dist[N];        // dist[x]存储1到x的最短路距离

struct Edge     // 边,a表示出点,b表示入点,w表示边的权重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}
  • Java
//注意在模板题中需要对下面的模板稍作修改,加上备份数组,详情见模板题
//一般的最短路问题中不需要备份距离数组,只有当有边数限制时才需要。
public static int n, m;
public static int[] dist = new int[N];
public static Edge[] edges = new Edge[N];  //存每一条边

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
public static class Edge {
    public int a;  //出点
    public int b;  //入点
    public int w;  //权重
    
    public Edge(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = w;
    }
}
    
public static int bellman_ford() {
    //初始化
    Arrays.fill(dist, 0x3f3f3f3f);
    dist[1] = 0;
        
    //如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (i = 0; i < n; i++) {
        //只有当有边数限制时才需要,防止串联
        //back = Arrays.copyOf(dist, n + 1);
        
        for (int j = 0; j < m; j++) {
            int a = edges[j].a;
            int b = edges[j].b;
            int w = edges[j].w;
            if (dist[b] > dist/*back*/[a] + w) {
                dist[b] = dist/*back*/[a] + w;
            }
        }
    }
    //这里为什么是0x3f3f3f3f/2呢?
    //因为如果在n次内到不了n - 1和n这个两个点,而n - 1到n的权重为负数,理应来说他们对应的dist值是0x3f3f3f3f,但是我在遍历并更新每条边时dist[n](0x3f3f3f3f) > dist[n-1](0x3f3f3f3f) + w(负数),这时候dist[n]就会进行更新,使得其小于0x3f3f3f3f,当然这里的0x3f3f3f3f/2也只是个模板数,具体还是要根据循环的次数和边的数量和权重的最小值判断
    if (dist[n] > 0x3f3f3f3f / 2) {
        //-1也只是个模板数,因为最后也会有dist[n] == -1 的情况出现,要根据题目具体变通
        return -1;
    }
    
    return dist[n];
}

03 有边数限制的最短路

  • 题目

Snipaste_2023-03-15_20-28-19.png

  • 题解
import java.io.*;
import java.util.Arrays;

public class Main {
    public static final int N = 510;
    public static final int M = 10010;
    public static int[] dist = new int[N];
    public static Edge[] edges = new Edge[M];
    public static int n, m, k;

    public static class Edge {
        public int a;
        public int b;
        public int w;

        public Edge(int a, int b, int w) {
            this.a = a;
            this.b = b;
            this.w = w;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        k = Integer.parseInt(str1[2]);
        for (int i = 0; i < m; i++) {
            String[] str2 = br.readLine().split(" ");
            int x = Integer.parseInt(str2[0]);
            int y = Integer.parseInt(str2[1]);
            int z = Integer.parseInt(str2[2]);
            edges[i] = new Edge(x, y, z);
        }

        bellmanford();
        br.close();
        pw.close();
    }

    public static void bellmanford() {
        Arrays.fill(dist, 0x3f3f3f3f);
        dist[1] = 0;

        for (int i = 0; i < k; i++) {
            int[] back = Arrays.copyOf(dist, n + 1);

            for (int j = 0; j < m; j++) {
                int a = edges[j].a;
                int b = edges[j].b;
                int w = edges[j].w;
                dist[b] = dist[b] > back[a] + w ? back[a] + w : dist[b];
            }
        }

        if (dist[n] > 0x3f3f3f3f / 2) {
            System.out.println("impossible");
        } else {
            System.out.println(dist[n]);
        }
    }
}

spfa

求最短路

  • 队列优化的Bellman-Ford算法
  • 在Bellman-Ford算法中,我们注意到dist[b] = min(dist[b], dist[a] + w);在每次循环遍历中不会都更新一遍,特别的,只有当dist[a]变小后,dist[b]才会跟着更新,因此我们只需要将变小后的a点添加进队列,每次从队头弹出t,更新t的所有出边就行
  • 时间复杂度 平均情况下 O(m),最坏情况下 O(nm),n表示点数,m表示边数

Snipaste_2023-03-15_20-39-12.png

  • C++
int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
  • Java
public static int[] h = new int[N];
public static int[] e = new int[M];
public static int[] ne = new int[M];
public static int[] dist = new int[N];
public static int[] w = new int[M];
public static boolean[] st = new boolean[N];
public static int n, idx;

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
public static int spfa() {
    //初始化
    Arrays.fill(dist, 0x3f3f3f3f);
    dist[1] = 0;
    
    ArrayDeque<Integer> q = new ArrayDeque<>();
    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 j = e[i];
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                if (!st[j]) {      //如果队列中已存在j,则不需要将j重复插入
                    q.offer(j);
                    st[j] = true;
                }
            }
        }
    }
    
    if (dist[n] == 0x3f3f3f3f) {
        return -1;
    }
    return dist[n];
}

04 spfa求最短路

  • 题目

Snipaste_2023-03-15_23-18-30.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 100010;
    public static int[] h = new int[N];
    public static int[] e = new int[N];
    public static int[] ne = new int[N];
    public static int[] w = new int[N];
    public static int[] dist = new int[N];
    public static boolean[] st = new boolean[N];
    public static int idx, n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        idx = 0;
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int x = Integer.parseInt(str2[0]);
            int y = Integer.parseInt(str2[1]);
            int z = Integer.parseInt(str2[2]);
            add(x, y, z);
        }

        spfa();
        br.close();
        pw.close();
    }

    public static void add(int x, int y, int z) {
        e[idx] = y;
        ne[idx] = h[x];
        w[idx] = z;
        h[x] = idx++;
    }

    public static void spfa() {
        Arrays.fill(dist, 0x3f3f3f3f);
        dist[1] = 0;
        ArrayDeque<Integer> q = new ArrayDeque<>();
        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 j = e[i];
                if (dist[j] > dist[t] + w[i]) {
                    dist[j] = dist[t] + w[i];
                    if (!st[j]) {
                        st[j] = true;
                        q.offer(j);
                    }
                }
            }
        }

        if (dist[n] == 0x3f3f3f3f) {
            System.out.println("impossible");
        } else {
            System.out.println(dist[n]);
        }
    }
}

判断图中是否存在负环

  • 时间复杂度O(nm),n表示点数,m表示边数

  • C++

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N], cnt[N];        // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N];     // 存储每个点是否在队列中

// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
    // 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。

    queue<int> q;
    for (int i = 1; i <= n; i ++ )
    {
        q.push(i);
        st[i] = true;
    }

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

  • Java
public static int[] h = new int[N];
public static int[] e = new int[N];
public static int[] ne = new int[N];
public static int[] w = new int[N];
public static int[] dist = new int[N];  //存最短距离
public static int[] cnt = new int[N];   //存最短距离中经过的点数
public static boolean[] st = new boolean[N];
public static n, idx;

// 如果存在负环,则返回true,否则返回false。
public static boolean spfa() {
    // 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
    ArrayDeque<Integer> q = new ArrayDeque<>();
    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 j = e[i];
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) {  // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                    return true;
                }
                if (!st[j]) {
                    st[j] = true;
                    q.offer(j);
                }
            }
        }
    }
    return false;
}

05 spfa判断负环

  • 题目

Snipaste_2023-03-16_00-02-46.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 2010;
    public static final int M = 10010;
    public static int[] h = new int[N];
    public static int[] e = new int[M];
    public static int[] ne = new int[M];
    public static int[] w = new int[M];
    public static int[] dist = new int[N];
    public static int[] cnt = new int[N];
    public static boolean[] st = new boolean[N];
    public static int idx, n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        idx = 0;
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int x = Integer.parseInt(str2[0]);
            int y = Integer.parseInt(str2[1]);
            int z = Integer.parseInt(str2[2]);
            add(x, y, z);
        }

        if (spfa()) {
            pw.println("Yes");
        } else {
            pw.println("No");
        }
        br.close();
        pw.close();
    }

    public static void add(int x, int y, int z) {
        e[idx] = y;
        w[idx] = z;
        ne[idx] = h[x];
        h[x] = idx++;
    }

    public static boolean spfa() {
        ArrayDeque<Integer> q = new ArrayDeque<>();
        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 j = e[i];
                if (dist[j] > dist[t] + w[i]) {
                    dist[j] = dist[t] + w[i];
                    cnt[j] = cnt[t] + 1;
                    if (cnt[j] >= n) {
                        return true;
                    }
                    if (!st[j]) {
                        st[j] = true;
                        q.offer(j);
                    }
                }
            }
        }
        return false;
    }
}

floyd

  • 时间复杂度:O(n^3^),n表示点数

  • C++

初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
  • Java
//初始化
for (int i = 1; i <= n; i ++) {
    for (int j = 1; j <= n; j ++) {
        if (i == j) {
            d[i][j] = 0;
        } else {
            d[i][j] = 0x3f3f3f3f;
        }
    }
}

// 算法结束后,d[a][b]表示a到b的最短距离
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++) {
                d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
}

06 Floyd求最短路

  • 题目

Snipaste_2023-03-16_19-05-55.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 210;
    public static int[][] d = new int[N][N];
    public static int n, m, k;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        k = Integer.parseInt(str1[2]);
        //初始化
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i == j) {
                    d[i][j] = 0;
                } else {
                    d[i][j] = 0x3f3f3f3f;
                }
            }
        }

        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int x = Integer.parseInt(str2[0]);
            int y = Integer.parseInt(str2[1]);
            int z = Integer.parseInt(str2[2]);
            d[x][y] = Math.min(d[x][y], z);
        }

        floyd();

        while (k-- > 0) {
            String[] str3 = br.readLine().split(" ");
            int x = Integer.parseInt(str3[0]);
            int y = Integer.parseInt(str3[1]);
            if (d[x][y] > 0x3f3f3f3f / 2) {
                pw.println("impossible");
            } else {
                pw.println(d[x][y]);
            }
        }
        br.close();
        pw.close();
    }

    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++) {
                    d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
                }
            }
        }
    }
}