【原创】求最短路径-SPFA算法

SPFA算法

引子

在很早很早以前,有个IOer兼BLOGer挖了一个坑:有一类问题,它要求我们从图上某点出发,走到另一点,求最短路径。
long long ago;我们学习了三种求最短路径的方法:

  1. Floyd算法
  2. Dijkstra算法
  3. Bellman-Ford算法

(如果不了解,可以点开上面的超链接看看(尽管我写的不好))
Floyd本质上是动态规划,Dijkstra算法本质上是贪心或者bfs,而Bellman-Ford的思路是枚举边,花了很多时间在没用的边上。能不能优化一下呢?
这就引出了我们今天的内容,SPFA算法。

算法的提出和背景

(虽然了解这些东西不会提高分数,但是可以学习计算机学家们的严谨精神,至少可以装装13
SPFA的全称是Shortest Path Faster Algorithm,意思是更快的最短路径算法。由西南交通大学段凡丁于1994年提出。
看看SPFA的名字,我们看到它口气真大,居然说自己更快,我们来看看它为什么更快。

算法的原理

在图G(V,E)(V为点集,E为边集)中,先用一个数组Dis[i]表示源点s到顶点i的最短路径经。初始化Dis清为∞,Dis[s]=0。
算法大致流程就是:

  1. 新建一个队列Q,将s入队
  2. 取队首元素,松弛它的临界点,如果松弛成功,则该临界点入队。(松弛操作见Bellman-Ford算法
  3. 当前队首元素出队。
  4. 如果队空,则找到最短路径,否则重复操作2。

算法正确性证明于时间复杂度分析

这是它的正确性的证明:
(摘自百度百科)

每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的Dis值就是对应结点的最短路径值。

它的时间复杂度号称是O(me),m是平均每点入队次数(注意,每点可能不止入队一次,可能经过松弛的点又会入队),有人证明说m一般不会超过2,但这个证明是十分不严谨的,并且SPFA算法十分不稳定,因此国际上一般不承认SPFA算法。国内有些老师也不会教。

负环判断

如果一个点入队次数超过n次,那么图有负环。

代码实现(伪代码)

int map[MAX][MAX];  
int dis[MAX];  //MAX为总点数
bool vis[MAX];  
int num[MAX];//记录每个结点入队的次数  
struct cmp  
{  
     bool operator()(int x,int y)  
     {  
          return x>y;  
     }  
};  
bool SPFA(int s0,int n)  
{  
     priority_queue<int,vector<int>,cmp> q;  
     memset(vis,false,sizeof(vis));  
     memset(num,0,sizeof(num));  
     for(int i=0;i<n;i++)  
          dis[i] = INF;  
     dis[s0] = 0;  
     q.push(s0);  
     vis[s0] = true;  
     num[s0]++;  
     while(!q.empty())  
     {  
          int p = q.top();  
          q.pop();  
          for(int i=0;i<n;i++)  
          {  
               if(dis[p]+map[p][i]<dis[i])  
               {  
                    dis[i] = dis[p]+map[p][i];  
                    if(!vis[i])  
                    {  
                         q.push(i);  
                         num[i]++;  
                         if(num[i]>n)//存在负环  
                         {  
                              return false;  
                         }  
                         vis[i]=true;  
                    }  
               }  
          }  
          vis[p] = false;  
     }  
     return true;  
}  

代码转自:http://blog.csdn.net/runninghui/article/details/8895586

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值