zoukankan      html  css  js  c++  java
  • SPFA解决单源最短路径

    SPFA(Shortest Path Faster Algorithm):

    一:基本算法

      在求解单源最短路径的时候,最经典的是 Dijkstra 算法,但是这个算法对于含有负权的图就无能为力了,而 Bellman - Ford 算法的复杂度又过于高,这时 SPFA就应运而生了. SPFA 在 Bellman - Ford 算法的基础上进行了改进,使其能够在计算带有负权的图的单源最短路径的基础上,时间复杂度大幅降低.

      众所周知 Bellman -Ford 算法会对每条边进行 n - 1 次检查,但是在这些检查过程中,有许多检查是没有必要的.事实上,唯一应该检查的边存在一个特点:这些边的起点在上一次处理时到源点的距离发生了变化,即松弛成功的边的终点.既然如此,那么就可以对算法进行优化,即每遍只处理特殊的点,这些点是上一次在松弛过程中某条边的终点.设立一个先进先出的队列来维护这些待处理的点,优化时每次取出队首节点 u, 并且对所有从点 u 出发的边进行松弛操作,对于每条边的终点 v,如果以点 v 为终点的边松弛成功, 且 v 不在队列内, 就将点 v 加入队尾.这样不断从对列取出节点来进行松弛操作,直至队列空为止.这个算法保证只要最短路径存在, SPFA 必定能求出最小值.

      SPFA同样可以判断负环,如果某个点弹出队列的次数超过 N - 1 次,则存在负环.对于存在负环的图,是无法计算出单源最短路径的.

    二:伪代码

      在以下说明中, inque[] 记录点是否在队列中, 使用链式前向星存图, 结果保留在 dist[] 中.

      (1):初始化,源的距离 dist[s] 设为 0, 其他点的距离设置为 INF, 新建一个队列, 将源点 s 入队, 标记源点 s 已经在对列中.

      (2):从队首取出一个点 top, 标记top出队, top 出队的次数加 1,并对这个次数检查, 如果大于 n, 说明出现负环, 结束算法. 否则遍历从点 top 出发的边, 如果边 k 的终点 to 的 dist[] 可以更新, 即 dist[ edge[k].to ] > dist[top] + edge[k].w, 则更新 dist[ edge[k].to ] = dist[top] + edge[k].w, 检查 to 是否在队列内, 如果不在则加入队列.

      (3)重复执行步骤(2), 直至队列为空.

    三:以图为列

    初始化队列、图和 dist[] 数组:

     

    1.选择队列首部的点 “5”,对从此点出发的边 <"5", "3"> 和  <"5", "4"> 进行松弛:

    2.选择队列首部的点 “3”,对从此点出发的边 <"3", “4”> 进行松弛, 松弛失败, 点 ”4“ 不会再次加入队列:

    3.选择队列首部的点 “4”,对从此点出发的边 <"4", "1"> 和 <"4", "6">进行松弛:

    4.选择队列首部的点 “1”,对从此点出发的边 <"1", '2"> 和 <"1", "6">进行松弛, 这里点”6“已经在队列内了,所以点 “6” 不会加入队列. 

    5.选择队列首部的点 “6”,对从此点出发的边<"6", "2"> 和 <"6", "5"> 进行松弛,.对边 <"6", "5"> 松弛失败, 虽然点 “5” 不在队列内, 但也不应该加入队列.

    6.选择队列首部的点 “2”,对从此点出发的边<"2", "3"> 和 <"2", "5"> 进行松弛,.对边 <"2", "5"> 松弛失败, 点 “5” 不应该加入队列.

    7.选择队列首部的点 “3”,对从此点出发的边<"3", "4"> 进行松弛,.且对边 <"3", "4"> 松弛失败, 此时队列为空算法结束.

    四:代码

     1 const int MAXN = 10000;
     2 const int MAXE = 100000;
     3 const int INF = INT_MAX;
     4 int dist[MAXN + 3];// dist[i] 表示点 i 到源点 s 的最短距离
     5 
     6 int head[MAXN + 3];//链式前向星存图
     7 struct NODE { int to; int w; int next; };
     8 NODE edge[MAXE];
     9 
    10 bool SPFA(int n, int s) {//s 为源点 n 为总的点数
    11     for(int i = 0; i <= n; i++) dist[i] = INF;//初始化
    12     dist[s] = 0;
    13     queue<int> rex;//保存待优化的节点
    14     rex.push(s);//从源点 s 开始优化s
    15     bool inque[MAXN + 3] = { false };//inque[i] = false 表示点 i 在队列内
    16     inque[s] = true;
    17     int outque[MAXN + 3] = { 0 };//记录每个点出对列的次数
    18     while(!rex.empty()) {
    19         int top = rex.front();//取队首的点为待拓展节点
    20         rex.pop();
    21         inque[top] = false;
    22         outque[top] ++;
    23         if(outque[top] > n) return false;//存在负环
    24         for(int k = head[top]; k != -1; k = edge[k].next) {//对从当前点出发的所有边进行松弛操作
    25             if(dist[top] != INF && dist[ edge[k].to ] > dist[top] + edge[k].w) {
    26                 dist[ edge[k].to ] = dist[top] + edge[k].w;
    27                 if(!inque[ edge[k].to ]) {//如果当前边另外一端的顶点不在队列内,则加入到队尾
    28                     rex.push( edge[k].to );
    29                     inque[ edge[k].to ] = true;
    30                 }
    31             }
    32         }
    33     }
    34     return true;
    35 }

    参考书籍 <<图论及应用>> 和网上部分资料.

  • 相关阅读:
    Golang
    Linux美化终端
    MetaSploit Pro 下载地址
    otunnel : 一个和lcx差不多的端口转发的工具
    MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption 2017-05-18 16:45
    黑客军火库
    图片后门捆绑利用工具 – FakeImageExploiter
    给自己的QQ群开启腾讯官方的群聊机器人
    ImportError: No module named Crypto.Cipher
    内网渗透小技巧
  • 原文地址:https://www.cnblogs.com/Ash-ly/p/5796558.html
Copyright © 2011-2022 走看看