Bellman-ford存在负权的单元点最短路径

一、算法介绍:

       为了能够求解边上带有负值的单源最短路径问题,Bellman(贝尔曼)和Ford(福特)提出了从源点逐次绕过其他顶点,以缩短到达终点的最短路径长度的方法。

       Bellman-ford算法是求解连通带权图中单源最短路径的一种常用算法,它允许图中存在权值为负的边。 同时它还能够判断出图中是否存在一个权值之和为负的回路。如果存在的话,图中就不存在最短路径(因为,假设存在最短路径的话,那么我们只要将这条最短路径沿着权值为负的环路再绕一圈,那么这条最短路径的权值就会减少了,所以不存在最短的路径,因为路径的最小值为负无穷),如果不存在的话,那么求出源点到所有节点的最短路径。

二、Bellman-Ford算法:

       初始d[起点]=0,其他点d值为∞。从1开始迭代路径长度上线为k,每次考虑图中的每条边(u,v)并检查是否有dv>du+w(u,v).若有,则更新dv的值。写成伪代码就是:

for k:=1 to n-1 do
   for 每条边(u,v) do
      if  (d[u]<∞) and (d[v]>d[u]+w(u,v))   then  d[v]:=d[u]+w(u,v)

松弛操作:每次比较dv和du+w(u,v)及更新dv叫做一次松弛操作。
时间复杂度:算法时间复杂度为O(nm)。

三、代码:

static final int MAX=99999;
int Edge[][];   //图的邻接矩阵
int vexnum; //顶点个数
void BellmanFord(int v)//v为起点
{//假定图的邻接矩阵和顶点个数已经读进来了
    int i, k, u;
    for(i=0; i< vexnum; i++)
    {
       dist[i]=Edge[v][i];  //对dist[]初始化
       if( i!=v && dis[i]< MAX ) 
          path[i] = v;//对path[ ]初始化
       else 
          path[i] = -1;
    }
        //如果dist[]各元素的初值为MAX,则循环应该n-1次,即k的初值应该改成1。
   for(k=2; k< vexnum; k++)
   { //从dist(1)[u]递推出dist(2)[u], …,dist(n-1)[u]
       bool flag=falsefor(u=0; u< vexnum; u++)
       {//修改每个顶点的dist[u]和path[u]
          if( u != v )  
          {
              for(i=0; i< vexnum; i++)
              {//考虑其他每个顶点
                  if( Edge[i][u]< MAX &&dist[u]>dist[i]+Edge[i][u] )
                  {
                      flag=true;
                      dist[u]=dist[i]+Edge[i][u];
                      path[u]=i;
                  }
              }
          }
       }
       if (!flag)
           break;//当迭代无任何值改变时,可直接退出循环
    }
 }

四、有无负权回路的判断

在bellman-ford算法求出结果之后,继续检查回路是否存在

for(i=0; i< vexnum; i++) { if( dist[u]>dist[i]+Edge[i][u] )
 {
 //存在回路
 }
}  

原理:若再次进行松弛操作的时候依然进入if判断语句,则一定存在负权回路,因为负权回路中每次

dist[u]>dist[i]+Edge[i][u]

总是成立

五、例题

http://poj.org/problem?id=1860
货币兑换,单元点最短路径
题目大意:
       给定N种货币,某些货币之间可以相互兑换,现在给定一些兑换规则,问能否从某一种货币开始兑换,经过一些中间货币之后,最后兑换回这种货币,并且得到的钱比之前的多。
思路:
       由于所有点之间都是双向联通,所以可以以初始货币为起点和终点,无需担心是否存在通路问题,只需要求出是否存在正权环路即可。因为存在正权回路时货币可以通过无限循环回路增加货币总量。
利用bellman-ford算法中,处理一遍之后进行回路判断,存在dis[edge[j][0]] – cost[j][1])*cost[j][0]>dis[edge[j][1]]则说明有正权回路。

六、Talk is cheap,show me the code(例题代码)

#include <iostream>
using namespace std;

const int Vertex = 110;
const int Edge = 210;

int cnt = 0;

int edge[Edge][2];
double cost[Edge][2];
double dis[Vertex];

bool Bellman(int N, int S, double V)
{
    int i,j;
    for (i = 1; i <= N; i++)
        dis[i] = 0;
    dis[S] = V;
    for (i = 1; i < N; i++)
    {
        bool flag = false;
        for (j = 0; j < cnt; j++)
        {
            if ((dis[edge[j][0]] - cost[j][1])*cost[j][0]>dis[edge[j][1]])
            {
                dis[edge[j][1]] = (dis[edge[j][0]] - cost[j][1])*cost[j][0];
                flag = true;
            }
        }
        if (!flag)
            return false;
    }
    for (j = 0; j < cnt; j++)
    {
        if ((dis[edge[j][0]] - cost[j][1])*cost[j][0]>dis[edge[j][1]])
            return true;
    }
    return false;
}

int main(void)
{
    int N, M, S;
    double V;
    cin >> N >> M >> S >> V;
    int i, j;
    for (i = 0; i < M; i++)
    {
        int a, b;
        double rab, cab, rba, cba;
        cin >> a >> b >> rab >> cab >> rba >> cba;
        edge[cnt][0] = a;
        edge[cnt][1] = b;
        cost[cnt][0] = rab;
        cost[cnt][1] = cab;
        cnt++;
        edge[cnt][0] = b;
        edge[cnt][1] = a;
        cost[cnt][0] = rba;
        cost[cnt][1] = cba;
        cnt++;
    }
    if (Bellman(N,S,V))
        cout << "YES" << endl;
    else
        cout << "NO" << endl;
    return 0;
}
点赞