单源加权图最短路径问题2-BellmanFord算法

之前提到Dijkstra算法不能解决权值为负的情况。    

Bellman-Ford算法能在更一般的情况下解决最短路径问题,即:允许权值为负。
注意,最短路径问题实际上均不允许有负值回路(当然是从源点可达的),因为这时不存在最短路径。总会有更短的办法-多绕负边回路走几趟就是了。
    
首先介绍一下松弛技术(Relaxation)
对每个顶点,都设置一个属性d[v], 用来描述从源点s到v的最短路径上权值的上界。
一步松弛操作的结果可以看作是对约束 d[v]<=d[u] + w(u,v)的松弛,即:每次比较dv和du+w(u,v)及更新dv叫做一次 松弛操做。        
通俗来说,松弛可以认为是求最短路径时,每一次优化路径的过程。
总结来看,引入松弛概念其实主要是为了抽象一类步骤优化操作,为了思考问题,写伪代码方便。比如Dijkstra写成含有Relax的算法形式为:
        void Dijkstra(Graph& g, VertexNode& s)
        {
            for each vertex v in g
            {
                v.distance = INFINITY;
                v.known = false;
                v.path = NULL;
            }
            s.distance = 0;
            for (int i=0; i
            {
                vertex v = smallest unknown distance vertex;
                if (v==NULL)
                    break;
                else
                    v.known = true;
                for each w adjacent to v
                {
                    if (!w.known)                    
                    {
                         Relax(w,v);                    
                    }
                }
            }
        }
    这样,Relax部分优化操作可以独立思考具体书写步骤。      
 
回到原话题,Dijkstra算法实际上相当于对每条边执行一次Relax操作,所以其算法复杂度为O(|E|+×)。也就是说,顶点known标记的改变,记录了每条边的松弛操作。而对于含有负边的图,known顶点会出现标记过早的情况,即一次Relax操作是不够的,所以Dijkstra算法不起作用。
Bellman-Ford算法的核心则是对每条边执行多次Relax操作,从而可以取消known的标记,因为顶点的known意向可能被之后的Relax操作改变。
Relax操作有一个非常重要的性质-路径松弛性质:如果p是一个从v0到vk的最短路径,而p的边是按照,...的顺序进行松弛的,那么d[vk]就是最短路径长度。这个性质的保持不受其他松弛操作的影响,即使他们与p的边上的松弛操作混在一起。
利用这个性质,我们很容易得出结论:对于一个图所有边均进行|V|-1次松弛操作,一定能得到最短路径。
    所以可以得到伪代码:
    BellmanFord(g, s)
    {
        for (int i=0; i < g.VertexNum; i++)
            for each edge(u, v) in g
                Relax(u, v);
    }
    算法复杂度为O(|E||V|)
    另外还有一种利用类似广度优先搜索算法实现该算法:
    这里其实相当于对上面的算法做了一个优化:如果某次对所有边进行Relax操作,没有任何d值变化,则可以立即退出迭代而不需要|V|-1次迭代都做完。
    void BellmanFord(Graph& g, Vertex& s)
    {
        queue q;
        for each vertex v in g
        {
           v.distance = INFINITY;
           v.path = NULL;
        }
        s.distance = 0;
        q.enqueue(s);
        while (!q.Empty())
        {
           v = dequeue(q);
           for each w adjenct to v
           {
               Relax(w,v)
               {   
                
                   if (w not in q)
                       q.enqueue(w);
               }
           }
        }   
    }

 

 代码实现:
#include
#include
#include

using namespace std;

#define MAX_VERTEX_NUM    20
#define INFINITY    2147483647
struct adjVertexNode
{
    int adjVertexPosition;
    int weight;
    adjVertexNode * next;
};
struct VertexNode
{
    char data [ 2 ];
    int distance;
    VertexNode * path;
    adjVertexNode * list;
};
struct Graph
{
    VertexNode VertexNode [ MAX_VERTEX_NUM ];
    int vertexNum;
    int edgeNum;
};

void CreateGraph ( Graph & g)
{
     int i , j , edgeStart , edgeEnd , edgeWeight;
     adjVertexNode * adjNode;
     cout << "Please input vertex and edge num (vnum enum):" << endl;
     cin >> g . vertexNum >> g . edgeNum;
     cout << "Please input vertex information (v1) /n note: every vertex info end with Enter" << endl;
     for ( i = 0; i < g . vertexNum; i ++)
     {
         cin >> g . VertexNode [ i ]. data; // vertex data info.
         g . VertexNode [ i ]. list = NULL;
     }
     cout << "input edge information(start end weight):" << endl;
     for ( j = 0; j < g . edgeNum; j ++)
     {
         cin >> edgeStart >> edgeEnd >> edgeWeight;
         adjNode = new adjVertexNode;
         adjNode -> weight = edgeWeight;
         adjNode -> adjVertexPosition = edgeEnd - 1; // because array begin from 0, so it is j-1
         // 将邻接点信息插入到顶点Vi的边表头部,注意是头部!!!不是尾部。
         adjNode -> next = g . VertexNode [ edgeStart - 1 ]. list;
         g . VertexNode [ edgeStart - 1 ]. list = adjNode;
     }
}

void PrintAdjList( const Graph & g)
{
    for ( int i = 0; i < g . vertexNum; i ++)
    {
        cout << g . VertexNode [ i ]. data << "->";
        adjVertexNode * head = g . VertexNode [ i ]. list;
        if ( head == NULL)
            cout << "NULL";
        while ( head != NULL)
        {
            cout << head -> adjVertexPosition + 1 << " ";
            head = head -> next;
        }
        cout << endl;
    }
}
void DeleteGraph( Graph & g)
{
    for ( int i = 0; i < g . vertexNum; i ++)
    {
        adjVertexNode * tmp = NULL;
        while( g . VertexNode [ i ]. list != NULL)
        {
            tmp = g . VertexNode [ i ]. list;
            g . VertexNode [ i ]. list = g . VertexNode [ i ]. list -> next;
            delete tmp;
            tmp = NULL;
        }
    }
}
void BellmanFord( Graph & g , VertexNode & s)
{
    deque < VertexNode *> q;
    for ( int i = 0; i < g . vertexNum; i ++)
    {
        g . VertexNode [ i ]. distance = INFINITY;
        g . VertexNode [ i ]. path = NULL;
    }
    s . distance = 0;
    q . push_back( &s);
   
    int counter = 0;
    while( ! q . empty())
    {
        VertexNode * v = q . front();
        q . pop_front();
        if( v == NULL)
            break;
       
        adjVertexNode * head = v -> list;
        while ( head != NULL)
        {
            VertexNode * w = & g . VertexNode [ head -> adjVertexPosition ];
            if( v -> distance + head -> weight < w -> distance)
            {
                w -> distance = v -> distance + head -> weight;
                w -> path = v;
                if ( find( q . begin (), q . end (), w) == q . end())
                {
                    q . push_back( w);
                }
            }
            head = head -> next;
        }
        counter ++;
        if ( counter > g . vertexNum * g . edgeNum)
        {
            cout << "This graph has minus value loop!" << endl;
            exit( 1);
        }
    }
}
void PrintPath( Graph & g , VertexNode * source , VertexNode * target)
{
    if ( source != target && target -> path == NULL)
    {
        cout << "There is no shortest path from " << source -> data << " to " << target -> data << endl;
    }
    else
    {
        if ( target -> path != NULL)
        {
            PrintPath( g , source , target -> path);
            cout << " ";
        }
        cout << target -> data ;
    }
}

int main( int argc , const char ** argv)
{
    Graph g;
    CreateGraph( g);
    PrintAdjList( g);
    VertexNode & start = g . VertexNode [ 0 ];
    VertexNode & end = g . VertexNode [ 6 ];
    BellmanFord( g , start);
    cout << "print the shortest path from v1 to v7" << endl;
    PrintPath( g , & start , & end);
    cout << endl;
    DeleteGraph( g);
    return 0;
}
     
 其中判断含有负值回路的方法很多,这里采用认为最多会有|V|×|E|次操作,否则存在负值回路。
 使用 queue也是可以的,但要注意 relax 内部判断要写成:

  if (find(q._Get_container().begin(), q._Get_container().end(), w) == q._Get_container().end())
  略显臃肿。 而实际上 queue也是一个container adapter,他内部封装了deque。

 

运行示例:
1 含有负值权边,但不含负值回路。

 

 单源加权图最短路径问题2-BellmanFord算法_第1张图片

运行结果

单源加权图最短路径问题2-BellmanFord算法_第2张图片

2 含有负值回路:v6->v7->v6 (-10)

单源加权图最短路径问题2-BellmanFord算法_第3张图片

运行结果

单源加权图最短路径问题2-BellmanFord算法_第4张图片

你可能感兴趣的:(算法)