图论算法-最短路

204 阅读2分钟

单源最短路径问题

image.png

  • 给定 有向图。(无向图也适用,他是特殊有向图么)。
  • 节点是连续的整数编号
  • (x,y,z) x-> y 是有向边,长度为 z 长度为 1 的话,我们选择 BFS 算法,非1 BFS 可能会出错
  • 1号起点
  • dist[1...n] 表示 1 到每一个节点最短的路径。

Bellman-Ford 算法 (基于 dp 和 迭代 思想)

算法流程

  • for所有边,dist[y] = min(dist[y], dist[x] + z )x.y 含义是 x到y的一条有向边 x起点,y终点。
  • 重复 n 次, 优化 : (没有更新就跳出)
  • 权值 :可以解决权值为负数的情况
  • 有解的情况 :图中没有负环
  • 适用场景 :擅长解决有边数限制的最短路问题。判断负环。
  • 时间复杂度 : O(n*m)。
  • 经过最多 k 条边的最短路径问题,这种问题只能使用Bellman-Ford算法
算法实现

题目来自 acwing 有边数限制的最短路

import java.util.*;
import java.io.*;

public class Main {
    
    static int INF = 0x3f3f3f3f;
    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        int n = sc.nextInt();
        int m = sc.nextInt();
        int k = sc.nextInt();
        // edg二维[0][1][2] 分别表示 起点 x 终点 y 和 边权 z
        int[][] edg = new int[m + 1][3];
        int[] dist = new int[n + 1];
        int[] back = new int[n + 1];
        for (int i = 1 ; i <= n ; i ++) {
            dist[i] = INF;
        }
        // 起点距离初始化为0
        dist[1] = 0;
        // 初始化图
        for (int i = 0 ; i < m ; i ++) {
            edg[i][0] = sc.nextInt();
            edg[i][1] = sc.nextInt();
            edg[i][2] = sc.nextInt();
        }
        
        for (int i = 0 ; i < k ; i ++) {
            boolean flag = false;
            back = Arrays.copyOf(dist,n + 1);
            for (int[] edge : edg) {
                int x = edge[0];
                int y = edge[1];
                int z = edge[2];
                // dist[y] = Math.min(dist[x] + z, dist[y]);
                if (dist[y] > back[x] + z) {
                    dist[y] = back[x] + z;
                    flag  = true;
                }
            }
            if (!flag) break;
        }

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

Dijkstar 算法

算法流程

  • 初始化 dist[i] = 0 , i 为起点。
  • 找出未被解锁的、dist[i]最小的点,解锁该点。第一次 为 1.
  • 扫描该点所有出边,dist[y] = (dist[y],dist[x] + z)
  • 直到全部被解锁。即可。
  • 应用场景 : 单元,超级源点,边的权值没有负数
  • 优化 : 优先级队列每次取最小的点。
  • 时间复杂度 O(m * log(n)) image.png
算法实现
import java.util.*;
import java.io.*;

public class Main {
    static int INF = 0x3f3f3f;
    public static void main(String[] args) throws IOException {
        BufferedReader read = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter wt = new PrintWriter(new OutputStreamWriter(System.out));
        String[] s = read.readLine().split(" ");
        int n = Integer.parseInt(s[0]);
        int m = Integer.parseInt(s[1]);
        // 邻接矩阵存储
        int[][] g = new int[n + 1][n + 1];
        for (int i = 1; i <= n ; i ++) {
            for (int j = 1; j <= n ; j ++)
                g[i][j] = INF;
        g[i][i] = 0;
        }
        
        while (m -- > 0) {
            s = read.readLine().split(" ");
            int x = Integer.parseInt(s[0]);
            int y = Integer.parseInt(s[1]);
            int z = Integer.parseInt(s[2]);
            g[x][y] = Math.min(g[x][y],z);
        }
        int[] dist = new int[n + 1];
        Arrays.fill(dist,INF);
        dist[1] = 0;
        boolean[] st = new boolean[n + 1];
        // 遍历 n 个点
        for (int i = 1 ; i <= n ; 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] == INF) wt.println(-1);
        else wt.println(dist[n]);
        wt.flush();
        wt.close();
        read.close();
    }
}

堆优化版dijstra

import java.util.*;
import java.io.*;

public class Main {
    
    static final int INF = (int)1e9 + 10;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        
        int n = sc.nextInt();
        int m = sc.nextInt();
        boolean[] v = new boolean[n + 1];
        int[] dist = new int[ n + 1];
        List<List<Integer>> edg = new ArrayList<>();
        List<List<Integer>> val = new ArrayList<>();
        
        for (int i = 0 ; i <= n ; i ++) {
            edg.add(new ArrayList<>());
            val.add(new ArrayList<>());
            dist[i] = INF;
        }
        
        while (m -- > 0) {
            int x = sc.nextInt();
            int y = sc.nextInt();
            int z = sc.nextInt();
            edg.get(x).add(y);
            val.get(x).add(z);
        }
        
        PriorityQueue<int[]> queue = new PriorityQueue<>((a,b) -> {return a[1] - b[1];}); 
        queue.offer(new int[]{1,0});
        dist[1] = 0;
        while (!queue.isEmpty()) {
            int[] top = queue.poll();
            int x = top[0];
            if (v[x]) continue;
            v[x] = true;
            for (int i = 0 ; i < edg.get(x).size() ; i ++ ) {
                int y = edg.get(x).get(i);
                int z = val.get(x).get(i);
                if (dist[y] > dist[x] + z) {
                    dist[y] = dist[x] + z;
                    queue.offer(new int[]{y,dist[y]});
                }
            }
        }
        System.out.println(dist[n] == INF ? -1 : dist[n]);
    }
}