热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

路径算法Dijistra,Floyd,Bellmanford,spfa

一、单元最短路径算法1.存图方式:邻接矩阵:适用于边数较多的稠密图使用,邻接表:适用于边数较少的稀疏图使用࿰

一、单元最短路径算法


1. 存图方式:

邻接矩阵:适用于边数较多的稠密图使用,

邻接表:适用于边数较少的稀疏图使用,当边数量接近点的数量, m=n

类:

class Edge {
  int start, end, weight;

  Edge(int _a, int _b, int _c) {
      start = _a;
      end = _b;
      weight = _c;
  }
}

2. Dijistra算法

2.1 朴素版

/**
* 贪心 O N^2
* 1.找距离最短的节点 2.计算该节点扩展的边
*
* 源节点加入最短路径集合,先算出这个节点的周围路径长度。 继续加入 不属于这个集合的且到这个集合 路径最短的节点,再算边
* 注意:graph是根据下标两点之间的值,如果到达不了则设置为Integer.MAXVALUE,不一定是lc题目那种
*
* P743网络延迟时间   https://leetcode.cn/problems/network-delay-time/solution/gong-shui-san-xie-yi-ti-wu-jie-wu-chong-oghpz/
* 存图方式
* 在开始讲解最短路之前,我们先来学习三种「存图」方式。
*
* 邻接矩阵
* 这是一种使用二维矩阵来进行存图的方式。
*
* 适用于边数较多的稠密图使用,当边数量接近点的数量的平方
*/
public class Dijkstra {

  public int[] dijkstra(int[][] graph, int n, int k) {
      int INF = Integer.MAX_VALUE;
      // 源节点到其他节点的最短距离
      int[] dist = new int[n];
      Arrays.fill(dist, INF);
      // 源节点到自身距离为0
      dist[k] = 0;

      // 最短路径节点集合
      boolean[] visited = new boolean[n];

      // 1.找距离最短的节点 2.计算该节点扩展的边
      // n -1 也行,是最后一轮循环确定倒数第二个节点时,也确定了到最后一个节点的边距离,可以省一轮遍历
      for (int i = 0; i           int x = findNextNode(dist, visited, n);
          // 源节点无法到达任务一个点
          if (x == -1) {
              return dist;
          }
          visited[x] = true;
          for (int y = 0; y               // 根据这个节点计算 最短路径数组 不通的边不考虑
              if (graph[x][y] != Integer.MAX_VALUE && dist[x] + graph[x][y]                   dist[y] = dist[x] + graph[x][y];
              }
          }
      }

      return dist;
  }

  // 首次默认是源节点,找到距离最短的节点 这里是遍历所有的点到该点距离
  private int findNextNode(int[] dist, boolean[] visit, int n) {
      int u = -1;
      int min = Integer.MAX_VALUE;
      for (int j = 0; j           if (!visit[j] && dist[j]               min = dist[j];
              u = j;
          }
      }
      return u;
  }

  // 下面也可以这样找最短节点 代替 findNextNode(),但是不通的节点也会加入计算,多余了点步骤
  //           int x = -1;
  //           for (int y = 0; y   //               if (!visited[y] && (x == -1 || dist[y]   //                   x = y;
  //               }
  //           }
}

2.2 堆优化版

通过堆替代 所有边找最短结点

/**
* 堆优化 - Dijkstra O(mlogn+n)
* 邻接表(数组)
* 这也是一种在图论中十分常见的存图方式,与数组存储单链表的实现一致(头插法)。
*
* 这种存图方式又叫链式前向星存图。
*
* 适用于边数较少的稀疏图使用,当边数量接近点的数量, m=n
* 下标0开始
*/
public class DijistraHeapMy {
  // 头节点指向的边
  private int[] srcToEdge;

  // 边指向节点
  private int[] edgeToDist;

  // 边的下一条边,同一个出发节点src   简称邻接边
  private int[] nextEdge;

  // 边的权重
  private int[] w;

  private int[] dist;

  private boolean[] visited;

  private int idx;

  private int INF = Integer.MAX_VALUE;

  private void add(int srcV, int distV, int weight) {
      this.edgeToDist[idx] = distV;
      this.nextEdge[idx] = this.srcToEdge[srcV];
      this.srcToEdge[srcV] = idx;
      this.w[idx] = weight;
      idx++;
  }

  private void init(int n) {
      visited = new boolean[n];
      dist = new int[n];
      w = new int[6000];
      nextEdge = new int[6000]; // 题目要求的边最多6000
      srcToEdge = new int[n]; // 头节点数量
      edgeToDist = new int[6000];
      Arrays.fill(srcToEdge, -1);
      Arrays.fill(nextEdge, -1);
      Arrays.fill(edgeToDist, -1);
      Arrays.fill(dist, INF);
      Arrays.fill(visited, false);
  }

  // k:源节点的下标,从0开始
  public int[] dijkstra(int[][] times, int n, int k) {
      // 初始化数组
      init(n);
      for (int[] ts : times) {
          // 从下标0开始
          add(ts[0] - 1, ts[1] - 1, ts[2]);
      }

      PriorityQueue queue &#61; new PriorityQueue<>((a, b) -> a[1] - b[1]);
      queue.add(new int[]{k, 0});
      dist[k] &#61; 0;

      while (!queue.isEmpty()) {
          // 确定点
          int v &#61; queue.poll()[0];
          if (visited[v]) {
              continue;
          }
          visited[v] &#61; true;

          // 确定最短集合边
          for (int i &#61; srcToEdge[v]; i !&#61; -1; i &#61; nextEdge[i]) {
              int distNode &#61; edgeToDist[i];

              System.out.println("结点" &#43; v &#43; " -> 结点" &#43; distNode);
              System.out.println("编号为" &#43; idx &#43; "的边, 权重为: " &#43; w[idx]);

              // 由于是通过边数组确定的&#xff0c;所以这里不用判断 !&#61;INF 因为都是能通的边
              if (w[i] &#43; dist[v]                   dist[distNode] &#61; w[i] &#43; dist[v];
                  queue.add(new int[]{distNode, dist[distNode]});
              }
          }
      }
      return dist;
  }
}

3. Floyd算法

/**
* 动态规划 - 邻接矩阵   O N^3
*
* 跑一遍 Floyd&#xff0c;可以得到「从任意起点出发&#xff0c;到达任意起点的最短距离   时间复杂度On 3 > Dijkstra On 2
*
* P743网络延迟时间Floyd
*
* 通过循环每个中转点求路径
*/
public class Floyd {
  private static int INF &#61; Integer.MAX_VALUE;

  /**
    * 距离矩阵   自身为0&#xff0c;到达不了为INF
    */
  public static int[][] distance;
  /**
    * 路径矩阵
    */
  public static int[][] path; // 一些求最短距离的点不需要记录路径矩阵也行

  public static void floyd(int[][] graph) {
      //初始化距离矩阵 distance
      distance &#61; graph;
      //初始化路径
      path &#61; new int[graph.length][graph.length];
      for (int i &#61; 0; i           for (int j &#61; 0; j               path[i][j] &#61; j;
          }
      }
      //开始 Floyd 算法
      //每个点为中转
      for (int i &#61; 0; i           //所有入度
          for (int j &#61; 0; j               //所有出度
              for (int k &#61; 0; k                   //以每个点为「中转」&#xff0c;刷新所有出度和入度之间的距离
                  //例如 AB &#43; BC                   if (graph[j][i] !&#61; INF && graph[i][k] !&#61; INF) {
                      // 如果两点到达不了也只能是 走中转节点
                      if (graph[j][i] &#43; graph[i][k]                           //刷新距离
                          graph[j][k] &#61; graph[j][i] &#43; graph[i][k];
                          //刷新路径
                          path[j][k] &#61; i;
                      }
                  }
              }
          }
      }
  }

  /**
    * 测试
    */
  public static void main(String[] args) {
//       int[][] graph &#61; new int[][]{
//           {0, 2, INF, 6}
//           , {2, 0, 3, 2}
//           , {INF, 3, 0, 2}
//           , {6, 2, 2, 0}};
      // 以下是转换后的 graph
      int[][] graph &#61; new int[][]{
          {0, 1, INF, INF}
          , {INF, 0, 1, INF}
          , {INF, INF, 0, 1}
          , {INF, INF, INF, 0}};

      floyd(graph);
      System.out.println("&#61;&#61;&#61;&#61;distance&#61;&#61;&#61;&#61;");
      // distance根据graph 修改为最短路径数组
      for (int[] ints : distance) {
          for (int anInt : ints) {
              System.out.print(anInt &#43; " ");
          }
          System.out.println();
      }
      System.out.println("&#61;&#61;&#61;&#61;path&#61;&#61;&#61;&#61;");
      for (int[] ints : path) {
          for (int anInt : ints) {
              System.out.print(anInt &#43; " ");
          }
          System.out.println();
      }
  }
}

4. Bellman-ford算法

理解 Bellman-ford算法_bellmanford算法_十七溯的博客-CSDN博客

防止串联原理 Bellman-ford算法详解_bellmanford算法_真的没事鸭的博客-CSDN博客

核心代码

//经过 n - 1次松驰
  //对所有边进行一次松弛操作&#xff0c;就求出了源点到所有点&#xff0c;经过的边数最多为1的最短路
//对所有边进行两次松弛操作&#xff0c;就求出了源点到所有点&#xff0c;经过的边数最多为2的最短路
//....
//对所有边进行n- 1次松弛操作&#xff0c;就求出了源点到所有点&#xff0c;经过的边数最多为n - 1的最短路
  for(int i &#61; 0; i        
      //遍历所有边
      for(int j &#61; 0; j           int a &#61; edges[j].a, b &#61; edges[j].b, w &#61; edges[j].w;
          if(dis[a] !&#61; 0x3f3f3f3f && dis[b] > dis[a] &#43; w) //松弛操作
              dis[b] &#61; dis[a] &#43; w;
      }
  }

5. SPFA算法

5.1 SPFA求最短路径

SPFA算法&#xff08;最短路径算法&#xff09;_钟一淼的博客-CSDN博客

1.用dis数组记录点到有向图的任意一点距离&#xff0c;初始化起点距离为0&#xff0c;其余点均为INF&#xff0c;起点入队。

2.判断该点是否存在。&#xff08;未存在就入队&#xff0c;标记&#xff09;

3.队首出队&#xff0c;并将该点标记为没有访问过&#xff0c;方便下次入队。

4.遍历以对首为起点的有向边&#xff08;t,i&#xff09;,如果dis[i]>dis[t]&#43;w(t,i),则更新dis[i]。

5.如果i不在队列中&#xff0c;则入队标记&#xff0c;一直到循环为空。

/**
* 对 BellmanFord 的优化 求最短路径   O M*N
* 能够解决负环问题&#xff1a; 负环&#xff0c;又叫负权回路&#xff0c;负权环&#xff0c;指的是一个图中存在一个环&#xff0c;里面包含的边的边权总和<0
*


* 与Dikstra类似&#xff0c;D是类似DFS找最短距离的点深入&#xff0c;SPFA是类似BFS每次从相邻节点扩展&#xff0c;而且访问过可以重复访问&#xff0c;可以解决负环问题
*


* https://blog.csdn.net/m0_64045085/article/details/123547253
*/
public class SpfaMy {
  // 边到节点
  private int[] edgeToNode;

  // 邻接边
  private int[] nextEdge;

  // 结点到边
  private int[] srcToEdge;

  // 边权重
  private int[] w;

  // 第几条边,从0开始
  private int idx;

  private boolean[] visited;

  private int INF &#61; Integer.MAX_VALUE;

  // 这里 k times 从下标1开始
  public int[] spfa(int[][] times, int n, int k) {
      // 初始化最短距离
      int[] dist &#61; new int[n &#43; 1];
      Arrays.fill(dist, INF);
      dist[k] &#61; 0;

      // 初始化是否访问过 由于下表从1开始所以n&#43;1
      visited &#61; new boolean[n &#43; 1];
      Arrays.fill(visited, false);
      visited[k] &#61; true;

      // 初始化表
      edgeToNode &#61; new int[6000]; // 题目最多6000条边
      nextEdge &#61; new int[6000];
      srcToEdge &#61; new int[n &#43; 1]; // 由于下表从1开始所以n&#43;1
      w &#61; new int[6000];

      // 初始化邻接表
      Arrays.fill(srcToEdge, -1);
      Arrays.fill(nextEdge, -1);
      for (int[] ts : times) {
          edgeToNode[idx] &#61; ts[1];
          nextEdge[idx] &#61; srcToEdge[ts[0]];
          srcToEdge[ts[0]] &#61; idx;
          w[idx] &#61; ts[2];
          idx&#43;&#43;;
      }

      // 初始化队列
      Deque queue &#61; new ArrayDeque<>();
      queue.add(k);

      while (!queue.isEmpty()) {
          System.out.print("队列此时为" &#43; queue);
          Integer poll &#61; queue.poll();
          visited[poll] &#61; false; // 这是能够探测负权的操作&#xff0c;如果原结点的距离更短&#xff0c;则最短路径继续能探测原结点
          System.out.println("&#xff0c;更新" &#43; poll &#43; "结点");

          // 循环点的邻接边 i是边 j是结点
          for (int i &#61; srcToEdge[poll]; i !&#61; -1; i &#61; nextEdge[i]) {
              int j &#61; edgeToNode[i];
              if (dist[poll] &#43; w[i]                   dist[j] &#61; dist[poll] &#43; w[i];
                  System.out.println("计算 " &#43; poll &#43; " 至 " &#43; j &#43; " &#xff0c;到 " &#43; j &#43; " 最短距离为 " &#43; dist[j]);

                  if (!visited[j]) {
                      queue.add(j);
                      visited[j] &#61; true;
                  }
              }
          }
      }
      return dist;
  }
}

5.2 SPFA求负环

852. spfa判断负环_51CTO博客_spfa判断负环

负环&#xff0c;又叫负权回路&#xff0c;负权环&#xff0c;指的是一个图中存在一个环&#xff0c;里面包含的边的边权总和<0

&#xff08;1&#xff09;(spfa)可以用来判断是不是有向图中存在负环。

&#xff08;2&#xff09;基本原理&#xff1a;利用抽屉原理

基于抽屉原理&#xff0c;如果一条正在搜索的最短路径上的点的个数大于总共点的个数&#xff0c;则说明路径上一定有至少重复的两个点&#xff0c;走了回头路。

(dist[x])的概念是指当前从(1)号点到(x)号点的最短路径的长度。(dist[x]&#61;dist[t]&#43;w[i]) (cnt[x])的概念是指当前从(1)号点到(x)号点的最短路径的边数量。(cnt[x]&#61;cnt[t]&#43;1) 如果发现(cnt[x]>&#61;n),就意味着从(1&#xff09;&#xff08;x)经历了(n)条边&#xff0c;那么必须经过了(n&#43;1)个点&#xff0c;但问题是点一共只有(n)个&#xff0c;所以必然有两个点是相同的&#xff0c;就是有一个环。 因为是在不断求最短路径的过程中发现了环&#xff0c;路径长度在不断变小的情况下发现了环&#xff0c;那么&#xff0c;只能是负环。 (1)号点是源节点的意思

&#xff08;3&#xff09;为什么初始化时初始值为(0),而且把所有结点都加入队列&#xff1f;

在原图的基础上新建一个虚拟源点&#xff0c;从该点向其他所有点连一条权值为(0)的有向边。那么原图有负环等价于新图有负环。此时在新图上做(spfa)&#xff0c;将虚拟源点加入队列中。然后进行(spfa)的第一次迭代&#xff0c;这时会将所有点的距离更新并将所有点插入队列中。执行到这一步&#xff0c;就等价于下面代码中的做法了。如果新图有负环&#xff0c;等价于原图有负环。

/**
* 对 BellmanFord 的优化 判断负环   O M*N
* 能够解决负环问题&#xff1a; 负环&#xff0c;又叫负权回路&#xff0c;负权环&#xff0c;指的是一个图中存在一个环&#xff0c;里面包含的边的边权总和<0
* https://blog.51cto.com/u_3044148/4028313
*


* 抽屉原理&#xff0c;如果一条正在搜索的[最短路径]上的点的个数大于总共点的个数&#xff0c;则说明路径上一定有至少重复的两个点&#xff0c;走了回头路
* 某个点的入队数大于了n&#xff0c;证明他在不停得松弛
*/
public class SpfaMyNegativeRing {
  class Edge {
      private int start;

      private int end;

      private int w;

      public Edge(int a, int b, int c) {
          this.start &#61; a;
          this.end &#61; b;
          this.w &#61; c;
      }
  }

  private List es &#61; new LinkedList<>();

  // 这里times 从下标0开始
  public boolean spfa(int[][] times, int n) {
      // 初始化队列
      Deque queue &#61; new ArrayDeque<>();
      for (int i &#61; 0; i           // 把所有点全部放入到队列中&#xff0c;因为我们不是要找从1点出发的负环&#xff0c;而是要找整个图中的负环
          // 每个点都相当于虚拟源点&#xff0c;只要从这个点出发的最短路径上&#xff0c;某个点入队超过N&#xff0c;就是有负环
          queue.add(i);
      }

      for (int[] ts : times) {
          es.add(new Edge(ts[0] - 1, ts[1] - 1, ts[2]));
      }

      // 都设置为0&#xff0c;只有负数的情况下才会是最短路径,没有负数就没有负环
      int[] dist &#61; new int[n];

      // 源点到下标点的最短路径的边数量
      int[] cnt &#61; new int[n];

      boolean[] visited &#61; new boolean[n];

      while (!queue.isEmpty()) {
          int poll &#61; queue.poll();
          visited[poll] &#61; false;

          // 此处不是邻接边&#xff0c;是所有边了
          for (Edge edge : es) {
              // 由于所有的最短路径 dist都是0&#xff0c;所以只有负数的才会是最短路径
              if (dist[poll] &#43; edge.w                   dist[edge.end] &#61; dist[poll] &#43; edge.w;

                  // 到达的点入队的数标为前面这个点入队的次数&#43;1 , 假设循环是好几个点负边连起来的就明白了&#xff0c; 1->2->3>1 权值都为负数
                  cnt[edge.end] &#61; cnt[poll] &#43; 1;
                  if (cnt[edge.end] >&#61; n) {
                      return false;
                  }

                  if (!visited[edge.end]) {
                      queue.add(edge.end);
                      visited[edge.end] &#61; true;
                  }
              }
          }
      }
      return true;
  }

  public static void main(String[] args) {
      System.out.println(new SpfaMyNegativeRing().spfa(new int[][]{{1, 2, 1}, {2, 3, 2}, {1, 3, 5}, {3, 4, 1},
          {2, 1, -1}}, 4));
  }
}





推荐阅读
  • BZOJ1233 干草堆单调队列优化DP
    本文介绍了一个关于干草堆摆放的问题,通过使用单调队列来优化DP算法,求解最多可以叠几层干草堆。具体的解题思路和转移方程在文章中进行了详细说明,并给出了相应的代码示例。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • JVM:33 如何查看JVM的Full GC日志
    1.示例代码packagecom.webcode;publicclassDemo4{publicstaticvoidmain(String[]args){byte[]arr ... [详细]
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • Python中程序员的面试题有哪些
    小编给大家分享一下Python中程序员的面试题有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有 ... [详细]
  • 介绍平常在多线程开发中,总避免不了线程同步。本篇就对net多线程中的锁系统做个简单描述。目录一:lock、Monitor1:基础 ... [详细]
  • PriorityQueue源码分析
     publicbooleanhasNext(){returncursor&amp;amp;lt;size||(forgetMeNot!null&amp;amp;am ... [详细]
author-avatar
YYYan1023
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有