class065 A星、Floyd、Bellman-Ford与SPFA【算法】

简介: class065 A星、Floyd、Bellman-Ford与SPFA【算法】

class065 A星、Floyd、Bellman-Ford与SPFA【算法】

2023-12-9 19:27:02

算法讲解065【必备】A星、Floyd、Bellman-Ford与SPFA

code1 A*算法模版

// A*算法模版(对数器验证)

package class065;
import java.util.PriorityQueue;
// A*算法模版(对数器验证)
public class Code01_AStarAlgorithm {
  // 0:上,1:右,2:下,3:左
  public static int[] move = new int[] { -1, 0, 1, 0, -1 };
  // Dijkstra算法
  // grid[i][j] == 0 代表障碍
  // grid[i][j] == 1 代表道路
  // 只能走上、下、左、右,不包括斜线方向
  // 返回从(startX, startY)到(targetX, targetY)的最短距离
  public static int minDistance1(int[][] grid, int startX, int startY, int targetX, int targetY) {
    if (grid[startX][startY] == 0 || grid[targetX][targetY] == 0) {
      return -1;
    }
    int n = grid.length;
    int m = grid[0].length;
    int[][] distance = new int[n][m];
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        distance[i][j] = Integer.MAX_VALUE;
      }
    }
    distance[startX][startY] = 1;
    boolean[][] visited = new boolean[n][m];
    PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[2] - b[2]);
    // 0 : 行
    // 1 : 列
    // 2 : 从源点出发到达当前点的距离
    heap.add(new int[] { startX, startY, 1 });
    while (!heap.isEmpty()) {
      int[] cur = heap.poll();
      int x = cur[0];
      int y = cur[1];
      if (visited[x][y]) {
        continue;
      }
      visited[x][y] = true;
      if (x == targetX && y == targetY) {
        return distance[x][y];
      }
      for (int i = 0, nx, ny; i < 4; i++) {
        nx = x + move[i];
        ny = y + move[i + 1];
        if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1 && !visited[nx][ny]
            && distance[x][y] + 1 < distance[nx][ny]) {
          distance[nx][ny] = distance[x][y] + 1;
          heap.add(new int[] { nx, ny, distance[x][y] + 1 });
        }
      }
    }
    return -1;
  }
  // A*算法
  // grid[i][j] == 0 代表障碍
  // grid[i][j] == 1 代表道路
  // 只能走上、下、左、右,不包括斜线方向
  // 返回从(startX, startY)到(targetX, targetY)的最短距离
  public static int minDistance2(int[][] grid, int startX, int startY, int targetX, int targetY) {
    if (grid[startX][startY] == 0 || grid[targetX][targetY] == 0) {
      return -1;
    }
    int n = grid.length;
    int m = grid[0].length;
    int[][] distance = new int[n][m];
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        distance[i][j] = Integer.MAX_VALUE;
      }
    }
    distance[startX][startY] = 1;
    boolean[][] visited = new boolean[n][m];
    // 0 : 行
    // 1 : 列
    // 2 : 从源点出发到达当前点的距离 + 当前点到终点的预估距离
    PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[2] - b[2]);
    heap.add(new int[] { startX, startY, 1 + f1(startX, startY, targetX, targetY) });
    while (!heap.isEmpty()) {
      int[] cur = heap.poll();
      int x = cur[0];
      int y = cur[1];
      if (visited[x][y]) {
        continue;
      }
      visited[x][y] = true;
      if (x == targetX && y == targetY) {
        return distance[x][y];
      }
      for (int i = 0, nx, ny; i < 4; i++) {
        nx = x + move[i];
        ny = y + move[i + 1];
        if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1 && !visited[nx][ny]
            && distance[x][y] + 1 < distance[nx][ny]) {
          distance[nx][ny] = distance[x][y] + 1;
          heap.add(new int[] { nx, ny, distance[x][y] + 1 + f1(nx, ny, targetX, targetY) });
        }
      }
    }
    return -1;
  }
  // 曼哈顿距离
  public static int f1(int x, int y, int targetX, int targetY) {
    return (Math.abs(targetX - x) + Math.abs(targetY - y));
  }
  // 对角线距离
  public static int f2(int x, int y, int targetX, int targetY) {
    return Math.max(Math.abs(targetX - x), Math.abs(targetY - y));
  }
  // 欧式距离
  public static double f3(int x, int y, int targetX, int targetY) {
    return Math.sqrt(Math.pow(targetX - x, 2) + Math.pow(targetY - y, 2));
  }
  // 为了测试
  public static int[][] randomGrid(int n) {
    int[][] grid = new int[n][n];
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        if (Math.random() < 0.3) {
          // 每个格子有30%概率是0
          grid[i][j] = 0;
        } else {
          // 每个格子有70%概率是1
          grid[i][j] = 1;
        }
      }
    }
    return grid;
  }
  // 为了测试
  public static void main(String[] args) {
    int len = 100;
    int testTime = 10000;
    System.out.println("功能测试开始");
    for (int i = 0; i < testTime; i++) {
      int n = (int) (Math.random() * len) + 2;
      int[][] grid = randomGrid(n);
      int startX = (int) (Math.random() * n);
      int startY = (int) (Math.random() * n);
      int targetX = (int) (Math.random() * n);
      int targetY = (int) (Math.random() * n);
      int ans1 = minDistance1(grid, startX, startY, targetX, targetY);
      int ans2 = minDistance2(grid, startX, startY, targetX, targetY);
      if (ans1 != ans2) {
        System.out.println("出错了!");
      }
    }
    System.out.println("功能测试结束");
    System.out.println("性能测试开始");
    int[][] grid = randomGrid(4000);
    int startX = 0;
    int startY = 0;
    int targetX = 3900;
    int targetY = 3900;
    long start, end;
    start = System.currentTimeMillis();
    int ans1 = minDistance1(grid, startX, startY, targetX, targetY);
    end = System.currentTimeMillis();
    System.out.println("运行dijskra算法结果: " + ans1 + ", 运行时间(毫秒) : " + (end - start));
    start = System.currentTimeMillis();
    int ans2 = minDistance2(grid, startX, startY, targetX, targetY);
    end = System.currentTimeMillis();
    System.out.println("运行A*算法结果: " + ans2 + ", 运行时间(毫秒) : " + (end - start));
    System.out.println("性能测试结束");
  }
}

code2 P2910 [USACO08OPEN] Clear And Present Danger S

// Floyd算法模版(洛谷)

// 测试链接 : https://www.luogu.com.cn/problem/P2910

// 请同学们务必参考如下代码中关于输入、输出的处理

// 这是输入输出处理效率很高的写法

// 提交以下所有代码,把主类名改成Main,可以直接通过

package class065;
// Floyd算法模版(洛谷)
// 测试链接 : https://www.luogu.com.cn/problem/P2910
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下所有代码,把主类名改成Main,可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Code02_Floyd {
  public static int MAXN = 101;
  public static int MAXM = 10001;
  public static int[] path = new int[MAXM];
  public static int[][] distance = new int[MAXN][MAXN];
  public static int n, m, ans;
  // 初始时设置任意两点之间的最短距离为无穷大,表示任何路不存在
  public static void build() {
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        distance[i][j] = Integer.MAX_VALUE;
      }
    }
  }
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StreamTokenizer in = new StreamTokenizer(br);
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    while (in.nextToken() != StreamTokenizer.TT_EOF) {
      n = (int) in.nval;
      in.nextToken();
      m = (int) in.nval;
      for (int i = 0; i < m; i++) {
        in.nextToken();
        path[i] = (int) in.nval - 1;
      }
      // 这道题给的图是邻接矩阵的形式
      // 任意两点之间的边权都会给定
      // 所以显得distance初始化不太必要
      // 但是一般情况下,distance初始化一定要做
      build();
      for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
          in.nextToken();
          distance[i][j] = (int) in.nval;
        }
      }
      floyd();
      ans = 0;
      for (int i = 1; i < m; i++) {
        ans += distance[path[i - 1]][path[i]];
      }
      out.println(ans);
    }
    out.flush();
    out.close();
    br.close();
  }
  public static void floyd() {
    // O(N^3)的过程
    // 枚举每个跳板
    // 注意,跳板要最先枚举!跳板要最先枚举!跳板要最先枚举!
    for (int bridge = 0; bridge < n; bridge++) { // 跳板
      for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
          // i -> .....bridge .... -> j
          // distance[i][j]能不能缩短
          // distance[i][j] = min ( distance[i][j] , distance[i][bridge] + distance[bridge][j])
          if (distance[i][bridge] != Integer.MAX_VALUE 
              && distance[bridge][j] != Integer.MAX_VALUE
              && distance[i][j] > distance[i][bridge] + distance[bridge][j]) {
            distance[i][j] = distance[i][bridge] + distance[bridge][j];
          }
        }
      }
    }
  }
}

code3 787. K 站中转内最便宜的航班

// Bellman-Ford算法应用(不是模版)

// k站中转内最便宜的航班

// 有 n 个城市通过一些航班连接。给你一个数组 flights

// 其中 flights[i] = [fromi, toi, pricei]

// 表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。

// 现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线

// 使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。

// 测试链接 : https://leetcode.cn/problems/cheapest-flights-within-k-stops/

package class065;
import java.util.Arrays;
// Bellman-Ford算法应用(不是模版)
// k站中转内最便宜的航班
// 有 n 个城市通过一些航班连接。给你一个数组 flights
// 其中 flights[i] = [fromi, toi, pricei]
// 表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
// 现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线
// 使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。
// 测试链接 : https://leetcode.cn/problems/cheapest-flights-within-k-stops/
public class Code03_BellmanFord {
  // Bellman-Ford算法
  // 针对此题改写了松弛逻辑,课上讲了细节
  public static int findCheapestPrice(int n, int[][] flights, int start, int target, int k) {
    int[] cur = new int[n];
    Arrays.fill(cur, Integer.MAX_VALUE);
    cur[start] = 0;
    for (int i = 0; i <= k; i++) {
      int[] next = Arrays.copyOf(cur, n);
      for (int[] edge : flights) {
        // a -> b , w
        if (cur[edge[0]] != Integer.MAX_VALUE) {
          next[edge[1]] = Math.min(next[edge[1]], cur[edge[0]] + edge[2]);
        }
      }
      cur = next;
    }
    return cur[target] == Integer.MAX_VALUE ? -1 : cur[target];
  }
}

P3385 【模板】负环

// Bellman-Ford + SPFA优化模版(洛谷)

// 给定一个 n个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环

// 负环的定义是:一条边权之和为负数的回路。

// 测试链接 : https://www.luogu.com.cn/problem/P3385

// 请同学们务必参考如下代码中关于输入、输出的处理

// 这是输入输出处理效率很高的写法

// 提交以下所有代码,把主类名改成Main,可以直接通过

package class065;
// Bellman-Ford + SPFA优化模版(洛谷)
// 给定一个 n个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环
// 负环的定义是:一条边权之和为负数的回路。
// 测试链接 : https://www.luogu.com.cn/problem/P3385
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下所有代码,把主类名改成Main,可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
public class Code04_SPFA {
  public static int MAXN = 2001;
  public static int MAXM = 6001;
  // 链式前向星建图需要
  public static int[] head = new int[MAXN];
  public static int[] next = new int[MAXM];
  public static int[] to = new int[MAXM];
  public static int[] weight = new int[MAXM];
  public static int cnt;
  // SPFA需要
  public static int MAXQ = 4000001;
  // 源点出发到每个节点的距离表
  public static int[] distance = new int[MAXN];
  // 节点被松弛的次数
  public static int[] updateCnt = new int[MAXN];
  // 哪些节点被松弛了放入队列
  public static int[] queue = new int[MAXQ];
  public static int l, r;
  // 节点是否已经在队列中
  public static boolean[] enter = new boolean[MAXN];
  public static void build(int n) {
    cnt = 1;
    l = r = 0;
    Arrays.fill(head, 1, n + 1, 0);
    Arrays.fill(enter, 1, n + 1, false);
    Arrays.fill(distance, 1, n + 1, Integer.MAX_VALUE);
    Arrays.fill(updateCnt, 1, n + 1, 0);
  }
  public static void addEdge(int u, int v, int w) {
    next[cnt] = head[u];
    to[cnt] = v;
    weight[cnt] = w;
    head[u] = cnt++;
  }
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StreamTokenizer in = new StreamTokenizer(br);
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    in.nextToken();
    int cases = (int) in.nval;
    for (int i = 0, n, m; i < cases; i++) {
      in.nextToken(); n = (int) in.nval;
      in.nextToken(); m = (int) in.nval;
      build(n);
      for (int j = 0, u, v, w; j < m; j++) {
        in.nextToken(); u = (int) in.nval;
        in.nextToken(); v = (int) in.nval;
        in.nextToken(); w = (int) in.nval;
        if (w >= 0) {
          addEdge(u, v, w);
          addEdge(v, u, w);
        } else {
          addEdge(u, v, w);
        }
      }
      out.println(spfa(n) ? "YES" : "NO");
    }
    out.flush();
    out.close();
    br.close();
  }
  // Bellman-Ford + SPFA优化的模版
  public static boolean spfa(int n) {
    distance[1] = 0;
    updateCnt[1]++;
    queue[r++] = 1;
    enter[1] = true;
    while (l < r) {
      int u = queue[l++];
      enter[u] = false;
      for (int ei = head[u], v, w; ei > 0; ei = next[ei]) {
        v = to[ei];
        w = weight[ei];
        if (distance[u] + w < distance[v]) {
          distance[v] = distance[u] + w;
          if (!enter[v]) {
            if (updateCnt[v]++ == n) {
              return true;
            }
            queue[r++] = v;
            enter[v] = true;
          }
        }
      }
    }
    return false;
  }
}

2023-12-9 21:16:55

相关文章
|
10月前
|
算法
Bellman-Ford算法求最短路和负环
Bellman-Ford算法求最短路和负环
53 0
Bellman-Ford算法求最短路和负环
|
12月前
|
算法
搜索与图论 - bellman-ford 算法
搜索与图论 - bellman-ford 算法
|
存储 算法
最短路径——Bellman-Ford算法以及SPFA算法
最短路径——Bellman-Ford算法以及SPFA算法
最短路径——Bellman-Ford算法以及SPFA算法
|
算法
|
算法
零基础学法100天第2天——bellman-ford(边数限制最短路算法)(下)
零基础学算法100天第2天——bellman-ford(边数限制最短路算法)
107 0
零基础学法100天第2天——bellman-ford(边数限制最短路算法)(下)
|
算法 Java
零基础学算法100天第2天——bellman-ford(边数限制最短路算法)(上)
零基础学算法100天第2天——bellman-ford(边数限制最短路算法)
434 0
零基础学算法100天第2天——bellman-ford(边数限制最短路算法)(上)