• 2-27 最短路径《啊哈算法》2-28完成四种算法


    第一节 只有五行的算法Floyd算法

     问题:求任意两个城市之间的最短路径?多源最短路径。

     解法1:O(n ²)的深度或广度遍历优先搜索

     解法2:Floyd,O(n³)

    Floyd 算法:从i号点到j 号点最多经过k个点的最短路径。--一种动态规划的思想

    可以处理边的值为负数的图,但不能处理有负权回路的图。

    核心是五行代码 

     答案:

    map = [
      nil,
      [nil, 0, 2, 6, 4],
      [nil, 999, 0, 3, 999],
      [nil, 7, 999, 0, 1],
      [nil, 5, 999, 12, 0]
    ]
    # floyd核心5行 ,从i到j的最短路程
    i = j = k = 1
    for k in 1..4 do
      for i in 1..4 do
        for j in 1..4 do
          if map[i][j] > map[i][k] + map[k][j]
            map[i][j] = map[i][k] + map[k][j]
          end
        end
      end
    end
    # 输出结果
    for i in 1..4 do
      for j in 1..4 do
        printf("%4d",map[i][j])
      end
      print " "
    end

    第2节 Dijkstra算法--单源最短路径 

    基本思想:每轮找到离起源点最近的一个点,然后以该点为中心进行扩散,通过该点相邻边的“松弛”,得到起源点到其余各点的估计路程,如此反复循环最终得到起源点到其余所有点的最短路径。 

    时间复杂度 O(n ²),不能解决负权边的问题。

    边数远小于n ²的图称为稀疏图,可以用邻接表代替邻接矩阵是时间复杂度优化(邻接表暂时没学会。)

    过程:

    1. 将所有点分为两部分,已知最短路径集合P,未知最短路径的集合Q。最开始P中只有起源点一个点。用book数组记录,索引代表各点,如果值为1则代表在P中,如果值为0代表在Q中。
    2. 用dis数组记录起源点到其余各点的初始路径。自己到自己dis[start] = 0, 和起源点直接相连的点i,dis[i]=map[start][i],同时把不直接相连的点的路径设为无穷∞。999
    3. 在Q中的所有点中选择距离起源点start最近的点u(dis[u]最小),加入到集合P中。
    4. 考察u的所有出边,对每条边松弛操作。例如存在u->v的边,那么start->v的距离是dis[u] + map[u][v]. 用这个距离和dis[v]比较。存较小的值到dis[v]中。
    5. 重复以上步骤,直到Q为空,算法结束。最终dis中的值就是起源点到所有其他点的最短路径。
    map = [
    nil,
    [nil, 0, 1, 12, 999, 999, 999],
    [nil, 999, 0, 9, 3, 999, 999],
    [nil, 999, 999, 0, 999, 5, 999],
    [nil, 999, 999, 4, 0, 13, 15],
    [nil, 999, 999, 999, 999, 0, 4],
    [nil, 999, 999, 999, 999, 999, 0]
    ]
    # 初始化dis数组,记录start起源点到其余各点的初始路程
    dis = [nil]
    print "输入起源点 "
    start = gets.to_i
    i= 1
    for i in 1..6 do
      dis[i] = map[start][i]  #这里起源点是start
    end
    p "初始化dis:#{dis},起源点是#{start}"
    # book初始化,用于记录已知最短路径的顶点集合,用1表示已知,0表示未知。
    book = [nil]
    for i in 1..6 do
      book[i] = 0
    end
    book[start] = 1
    ###################Dijkstra算法核心
    n = 6 #6个点
    # 第6点无出边,所以是n -1 次循环,否侧改为n
    i = 1
    while i <= n -1
      print "第#{i}次外循环:#{dis} "
      # 找到离起源点最近的点
      min = 999999
      j = 1
      while j <= n
        if book[j] == 0 && dis[j] < min
          min = dis[j]
          # u点为离起源点最近的点。
          u = j
        end
        j += 1
      end
      # 标记放入已知最短路径集合
      book[u] = 1
      # 对u的所有出边连接的点v,进行松弛操作
      v = 1
      while v <= n
        # 判断u和v是否相连
        if map[u][v] < 999
          # 判断是否是更近的路线,如果是更新dis[v]
          if dis[v] > dis[u] + map[u][v]
            dis[v] = dis[u] + map[u][v]
          end
        end
        v += 1
      end
      i += 1
    end
    p "单源点#{start}到其余各点的最短路径:"
    i = 1
    for i in 1..6 do
      p "#{start}到#{i}的最短路径是#{dis[i]}"
    end

    第三节 bellman-ford --可以解决负边的问题。 

    储存数据用邻接表,a,b,r三个数组,索引值代表每条边的数据,值储存a点,b点,和a->b的权值。

    dis数组,储存起始点到其他点的权重。初始化为[0, 999,999,999,999。。。] 

    如第i条边储存在a[i]->b[i],权值r[i]. 

    核心代码:

    for k in 1..n-1 do

      for i in 1..m do

        if dis[[b[i]]] > dis[a[i]] + r[i]

          dis[[b[i]]] = dis[a[i]] + r[i]

        end 

      end 

    end 

    主要思想:求单源点到其他点的最短路径。这里是用来松弛。第一轮得到起源点只通过1条边到其他点的最短路径,第2轮是起源点最多经过2条边到其他点的最短路径。。。第k轮,起源点最多经过k条边到达其余各点的最短路径。总共n-1轮。(起源点1各,其他点n-1个,任意两地之间的最短边至多n-1个)

    一句话,对所有边进行n-1次松弛。 

    a = [nil,2, 1, 1, 4, 3]
    b = [nil,3, 2, 5, 5, 4]
    r = [nil,2, -3, 5, 2, 3]
    # 初始化dis
    dis = []
    i = 1
    for i in 1..n do
      dis[i] = 999
    end
    # 设置1号点,
    dis[1] = 0
    p dis
    # 核心算法,起始点经过k条边到达其他各点的最短路径dis[i]
    i = k = 1
    for k in 1..n-1 do
      for i in 1..m do
        if dis[b[i]] > dis[a[i]] + r[i]
          dis[b[i]] = dis[a[i]] + r[i]
        end
      end
    end
    # 结果
    p dis  #=> [nil, 0, -3, -1, 2, 4]

    判断是否有负权回路:再对所有边进行一次松弛,看看是否可以成功。

    回路权值之和为正,正权回路。

    回路权值值和为负,负权回路。

    用check变量来检查变化,如果没有变化可以提前终止循环。

    n = 5 #点

    m = 5 #边
    a = [nil,2, 1, 1, 4, 3]
    b = [nil,3, 2, 5, 5, 4]
    r = [nil,2, -3, 5, 2, 3]
    # 初始化dis
    dis = []
    i = 1
    for i in 1..n do
      dis[i] = 999
    end
    # 设置1号点,
    dis[1] = 0
    p dis
    # 核心算法,起始点经过k条边到达其他各点的最短路径dis[i]
    i = k = 1
    for k in 1..n-1 do
      # 用check审查每轮松驰是否更新
      check = 0
      for i in 1..m do
        if dis[b[i]] > dis[a[i]] + r[i]
          dis[b[i]] = dis[a[i]] + r[i]
          check = 1
        end
      end
      # 如果没有更新dis ,则提前退出循环,算法结束。
      if check == 0
        break
      end
    end

    # 再进行一轮松弛,如果发生变化,则有负权回路。 

    flag = 0
    i = 1
    for i in 1..m do
      if dis[b[i]] > dis[a[i]] + r[i]
        flag = 1
      end
    end
    if flag == 1
      print "此图有负权回路"
    else
      # 输出结果
      p dis
    end

     第四节 bellman的优化算法。

    优化目标:对最短路径估计值发生变化的顶点的所有出边进行松弛操作。

    dis = [nil, 0 , 999,999,999,999] 

    使用队列queue,存储这些顶点。

    (使用邻接表简化)

    用队列优化的关键:只有那些在前一遍松弛中改变了最短路径估计值的顶点,才可能引起它们邻接点最短路径估计值发生变化。所以,用一个queue来储存被成功松弛的顶点,之后只对队列中的点进行松弛的判断和处理,这就降低了算法的时间复杂度,最多O(n*m) 

    已经确定了最短路径的点就不需要再做一遍松弛了。实际上轮数会小于n

    总结:开始时将源点加入queue。每次从队首(head)取出一个顶点,并对与其相邻的所有顶点进行松弛尝试,如果某个相邻点松弛成功,并且这个相邻点不在queue中(不在head和tail之间),则将它加入队列尾(然后tail+=1)。对当前顶点处理完毕后立即出队(head +=1)
    ,并对下一个新队首如上操作,直到队列为空时(head==tail)算法结束。


    n = 5 #点
    m = 7 #边
    a = [nil,1, 1, 2, 2, 3, 4, 5]
    b = [nil,2, 5, 3, 5, 4, 5, 3]
    r = [nil,2, 10, 3, 7, 4, 5, 6]
    # 初始化dis数组
    # 初始化book,用来标记,默认0,刚开始都不在队列中
    dis =[nil]
    book=[nil]
    i = 1
    for i in 1..n do
      dis[i] = 999
      book[i] = 0
    end
    dis[1] = 0 # 求第一个点到其他点的最短路径
    # 初始化first数组索引1~n的值为-1,表示顶点1~n暂时没有边被读入。
    # first数组记录了每个点最后存入的邻接边的编号。如果一个点没有邻接边默认为-1
    # before储存了所有的初始编号-1和first中没有储存的边的编号。
    # before + first 储存了所有边号和初始编号-1

    # before[i]储存了“编号为i的边”的“出点a”的"上一条邻接边"的编号。这里i是出点a的最后读入的邻接边。

    first = [nil]
    before = [nil]
    i = 1
    for i in 1..n do
      first[i] = -1
    end
    i = 1
    for i in 1..m do
      # 读入每条边,邻接表的关键代码:
      before[i] = first[a[i]]
      first[a[i]] = i
    end
    # 队列queue,用来记录点,这个点的出边可以执行松弛操作,即最短路径发生了变化
    queue = [nil]
    head = tail = 1
    # 1号点入列
    queue[head] = 1
    tail += 1
    while head < tail
      # 处理的队首点
      # k是边的编号。
      # 下行代码是当前队列的head指向的顶点在first数组中的最后写入的相邻边的编号
      k = first[queue[head]]
      while k != -1
        if dis[b[k]] > dis[a[k]] + r[k]
          # 松弛成功,则更新顶点1到顶点b[k]的路径
          dis[b[k]] = dis[a[k]] + r[k]
          # book判断b[k]点是否在队列中
          if book[b[k]] == 0 #不在队列
            queue[tail] = b[k]
            tail += 1
            book[b[k]] = 1 #表示b[k]已经入队
          end
        end
        # before[k]储存了“编号为k的边”的“出点a”的"上一条邻接边"的编号。
        k = before[k] 

        #为了循环再赋值给k变量。直到满足退出循环的条件k = -1,这时head指向的点的所有邻接边都进行过了松弛操作。

      end
      p dis
      # 出队
      book[queue[head]] = 0
      head += 1
    end
    p dis
  • 相关阅读:
    Callback2.0
    设计模式之Composite
    设计模式之Proxy
    React Native DEMO for Android
    React Native 与 夜神模拟器的绑定
    Skipping 'Android SDK Tools, revision 24.0.2'; it depends on 'Android SDK Platform-tools, revision 20' which was not installed.
    .ui/qrc文件自动生成.py文件
    简单排序算法
    Big O
    设计模式之Adapter
  • 原文地址:https://www.cnblogs.com/chentianwei/p/8477451.html
Copyright © 2020-2023  润新知