美文网首页
最短路径

最短路径

作者: 以梦为马驾驾驾 | 来源:发表于2022-04-04 21:55 被阅读0次

    图的最短路径

    只是个人的总结, 防止忘记

    定义: 找到一个点到另一个顶点成本最小的路径

    Dijkstra( 权重非负, 有无环都可)

    能够得到最短路径的算法, 只要证明自己可以放松所有的边, 直到都失效为止.

    对于朴素算法: 需要最终所有节点都被放松过

    对于有限队列优化的: 优先级最小的顶点的distTo[]值就是最短路径的权重, 它不会小于任意已经放松过的任意顶点的最短路径的权重, 也不会大于任何还未被放松过的任意顶点的最短路径的权重. 这样, 所有s可以到达的顶点的最短路径都按照最短路径的权重顺序被放松.

    如果没有环, 那可以用拓扑排序优化, 先拓扑排序, 然后按照顺序放松节点, 这是无环最优化的, 且可以处理负权重边.

    可以做的leetcode题目

    (https://leetcode-cn.com/problems/path-with-maximum-probability/)

    (https://leetcode-cn.com/problems/path-with-minimum-effort/)

    (https://leetcode-cn.com/problems/network-delay-time/)

    Dijkstra像是 BFS 和 PRIM 算法.

    朴素的Dijkstra:

    会放松所有的边, ans里的节点都会被访问到. 且可以看到一个节点的距离值可以被多次修改.

     // 3. dijkstra
      def dijkstraSolution(times: Array[Array[Int]], n:Int, k:Int): Int = {
        import scala.collection.mutable
        val tu = mutable.Map[Int, mutable.Map[Int, Int]]()
        (1 to n).foreach{ i => tu.put(i, mutable.Map[Int, Int]())}
        times.foreach{
          case Array(uid, vid, weight) => {
            tu(uid).put(vid, weight)
          }
        }
        val ans = mutable.Map[Int, Int]((1 to n).map( _ -> Integer.MAX_VALUE):_*) // 这个, ans的元素综合为n个和88行, 保证了全部能被访问到
        ans.update(k, 0)
    
        val visitedSet = mutable.Set[Int]()
        while(visitedSet.size != n) {
          val _@(miniNode, miniWeight): (Int, Int) = ans.filterNot(e => visitedSet.contains(e._1)).minBy(_._2)
          visitedSet.add(miniNode)
            //   println("visited: " + visitedSet.map(_.toString))
            //   println("ans" + ans.toMap)
          val adjs: mutable.Map[Int, Int] = tu(miniNode)
          adjs.foreach{
            case (adjNode, adjValue) => {
              ans(adjNode) =  math.min(adjValue + miniWeight,  ans(adjNode))
            //   ans.update(adjNode, math.min(adjValue + miniWeight,  ans(adjNode)))
            }
    
          }
        }
    
        if(ans.values.exists( _ == Integer.MAX_VALUE)) {
          -1
        }else{
          ans.values.max
        }
    
      }
    

    优先队列优化的

    在设置一个点的距离前, 会将所有之前放松其他边得到的可能最小距离值, 放到优先队列中.

    另外, 优先队列里的元素, 是修改它的值, 还是重新压入,使得队列中存在失效的数据. 这两种方式, 前者是即时实现, 后者是lazy的延时实现.(我没太明白算法4里的这个)

      // 4. optimized dijstra
      def optimized_dijstra(times: Array[Array[Int]], n: Int, k: Int) = {
        import scala.collection.mutable
        val tu = mutable.Map[Int, mutable.Map[Int, Int]]()
        (1 to n).foreach{ i => tu.put(i, mutable.Map[Int, Int]())}
        times.foreach{
          case Array(uid, vid, weight) => {
            tu(uid).put(vid, weight)
          }
        }
        val ans = mutable.Map[Int, Int]((1 to n).map( _ -> Integer.MAX_VALUE):_*)
        implicit val tupleOrdering: Ordering[(Int, Int)] = new Ordering[(Int, Int)]{
          override def compare(x: (Int, Int), y: (Int, Int)): Int = x._1 - x._2
        }
        val updateSignalPriorityQueue = mutable.PriorityQueue[(Int, Int)]()(tupleOrdering)
        updateSignalPriorityQueue.enqueue((k,0))
          
        while(updateSignalPriorityQueue.nonEmpty) { // method1
    
          val _@(miniNode, updatedWeight) = updateSignalPriorityQueue.dequeue()
    
          // 如果没有被更新过...
          // 被更新过
          if(ans(miniNode) == Integer.MAX_VALUE || updatedWeight < ans(miniNode)) { 
        //  if( ans(miniNode) == Integer.MAX_VALUE || !visitedSet.contains(miniNode)) {
            ans(miniNode) = updatedWeight
            // visitedSet.add(miniNode)
            tu(miniNode).foreach {
              case _@(adjV, adjW) => {
                updateSignalPriorityQueue.enqueue(adjV -> (updatedWeight + adjW))  // 这个方法的复杂度和对有限队列的操作成正比, updateWeight < ans(miniNOde)保证了失效的边不会再作用, 但也是个弹出操作, 而如果在
                                                                                  // 这里, 不用enqueue, 如果adjV存在于有限队列,修改它的值,并且,调整顺序, 总调整次数与弹出操作次数相同
              }
            }
          }else{
                ()
          }
        }
        if(ans.values.exists( _ == Integer.MAX_VALUE)) {
          -1
        }else{
          ans.values.max
        }
      }
    

    确定无环的用拓扑排序优化

    bfs 或者 dfs 的方式

    效率不高 , 可以忽略

    变种

    最长路径
    1. 转换视角, 非负权重图的最短路径就是 负权重值的最长路径 , 只要把原图的权重全部取反, 最后得到结果后再取反就好.
    2. 改变relax函数中的不等式方向, 原来是新的权重如果小于旧的权重更新, 现在改成, 新的权重如果大于旧的则更新

    两种方法都是转化了视角, 最短和最长是一对对偶.

    另外, 在加权有向图(权重可能为负数) 中寻找最长路径, 已知最好的算法的复杂度也是指数级别的, 而若是有环, 则更复杂了.

    并行任务调度

    优先级限制下的并行任务调度定义:

    1. 一组需要完成的任务和每个任务所需时间
    2. 一组关于任务完成的先后次序的优先级限制

    在满足此限制条件下, 在数量不限的处理器上安排这些任务, 使得任务最快完成, 且使用的处理器资源最少(可以理解为cpu不要忙等, 即给定任务要直接执行, 而不是还要再等它的前序先完成)最少.

    这个问题, 可以通过"关键路径"的方法证明它和 "无环加权有向图"的最长路径 问题等价

    将定义里的两个条件转换成对应的图:

    image.png image.png

    将任务转化成图的一条边, 边有两个点, 任务的时间就是边的权重, 而 "关键路径" 即条件二: 优先级次序 转化而来的, 关键路径之间转化的节点间也有路径, 不过权重为0 , 此外, 开始S具有0权重到任意任务的开始节点, 任意任务的结束节点有0权重到结束节点. 第二章图中的最长路径, 就是每个路径的开始节点的最长路径权重值就是它的任务开始时间.

    相对最后期限下的并行任务调度

    和上个情况不同的是, 多了一个限制类型: 某个任务需要在指定的时间点之前开始, 即指定和另一个任务的开始时间的相对时间. 举例: 任务2 必须在任务4 开始后的12个时间内启动.

    等价为; 加权有向图(可能环以及负权重边)的最短路径问题

    相关文章

      网友评论

          本文标题:最短路径

          本文链接:https://www.haomeiwen.com/subject/dsrhsrtx.html