单源最短路径算法之Bellman-Ford算法

一、算法简介

    其实Bellman-Ford算法比Dijkstra算法更加简单,适用面更广,但是算法效率不如Dijkstra.

    Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。这时候,就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼(Richard Bellman, 动态规划的提出者)和小莱斯特•福特(Lester Ford)发明。Bellman-Ford算法的流程如下:

单源最短路径算法之Bellman-Ford算法_第1张图片

一、Bellman-Ford与Dijkstra对比

单源最短路径算法之Bellman-Ford算法_第2张图片

单源最短路径算法之Bellman-Ford算法_第3张图片


下面是修改前文Dijkstra算法得到的Bellman-Ford算法,可以看出Bellman更简单,但是效率更低
void Bellman_Ford(    
              const int numOfVertex,    /*节点数目*/    
              const int startVertex,    /*源节点*/    
              int (map)[][6],          /*有向图邻接矩阵*/    
              int *distance,            /*各个节点到达源节点的距离*/    
              int *prevVertex           /*各个节点的前一个节点*/    
              )    
{    
   // vector<bool> isInS;                 //是否已经在S集合中     
    //isInS.reserve(0);    
   // isInS.assign(numOfVertex, false);   //初始化,所有的节点都不在S集合中 , 分配numOfVertex个字节   
        
    //step1  
 /*初始化distance和prevVertex数组*/    
    for(int i =0; i < numOfVertex; ++i)    
    {    
        distance[ i ] = map[ startVertex ][ i ];  //源节点到各个节点的距离,其中INT_MAX代表不可达  
        if(map[ startVertex ][ i ] < INT_MAX)  //如果是可达的  
            prevVertex[ i ] = startVertex;    
        else    
            prevVertex[ i ] = -1;       //表示还不知道前一个节点是什么     
    }    
    prevVertex[ startVertex ] = -1;  //源节点无前一个节点  
        
    /*开始使用贪心思想循环处理不在S集合中的每一个节点*/    
    //isInS[startVertex] = true;          //开始节点放入S集合中     
        
        
 int currentVertex = startVertex;  
        
    for (int i = 1; i < numOfVertex; i ++)      //这里循环从1开始是因为开始节点已经存放在S中了,还有numOfVertex-1个节点要处理     
    {    
        //step2    
        /*在Q中选择u到j的distance最小的一个节点, 如第一步A到C,最后目标到D*/     
       /* int minDistance = INT_MAX;    
        for(int j = 0; j < numOfVertex; ++j)  //  
        {    
            if((isInS[j] == false) && (distance[j] < minDistance))//寻找初始currentVertexA到Q中distance最小的节点 最后为新的currentVertexC    
            {    
                currentVertex = j;    
                minDistance = distance[j];    
            }    
        }    
        isInS[currentVertex] = true;//将这个节点currentVertex放入S集合中  * /   
            
         //step3  
        /*对这个新的currentVertexC做松弛计算,更新distance*/
		for(int start=0;start<numOfVertex;start++)
		{
			for (int j =0; j < numOfVertex; j ++)  
			{
				if (j!= startVertex  && map[start][j] < INT_MAX)  //在Q中,有距离的为c->d,c->e, c->b  
				{    
					int currentdist = distance[ start] + map[ start ][ j ];    
					if (currentdist < distance[ j ])  //distance[j = ]为开始到j的距离  
					{    
						distance[ j ] = currentdist;    
						prevVertex[ j ] = start;    
					}    
				}    
			} 
		}
    } 
	//判断是否存在环
	bool isLoopExist = false;
	for(int start=0;start<numOfVertex&& !isLoopExist;start++)
	{		
		for (int j =0; j < numOfVertex && !isLoopExist; j ++)  
		{    
            if (j!= startVertex  && map[start][j] < INT_MAX)  //在Q中,有距离的为c->d,c->e, c->b  
            {    
                int currentdist = distance[ start] + map[ start ][ j ];    
                if (currentdist < distance[ j ])  //distance[j = ]为开始到j的距离  
                {    
					isLoopExist = true;
                }    
			}    
		} 
	}
			if(!isLoopExist)
		{
			cout<<"no loop"<<endl;
		}
}

三、关于输出路径

1.可以使用栈,正如前文所示,还有可以使用递归输出

void printPath(int des)
{
    if(preVertex[des]!=-1) 
		printPath(preVertex[des]);
    cout<<des<<' ';

}

因为所有递归都可以转换为栈的操作

四、Bellman-Ford算法优化-SPFA

使用队列优先替换Bellman中的start循环,让每一次开始的仅仅是那些最有可能使结果更新的点

void SPFA(    
              const int numOfVertex,    /*节点数目*/    
              const int startVertex,    /*源节点*/    
              int (map)[][6],          /*有向图邻接矩阵*/    
              int *distance,            /*各个节点到达源节点的距离*/    
              int *prevVertex           /*各个节点的前一个节点*/    
              )    
{  
	//队列和判断index是否在队列的结构
	queue<int> q;
	bool isInQueue[6];
    //step1  
 /*初始化distance和prevVertex数组*/    
    for(int i =0; i < numOfVertex; ++i)    
    {   
		isInQueue[i] = false;
        distance[ i ] = INT_MAX;  //这个距离必须初始化 为无穷大,否则加入队列的操作不好判断
        if(map[ startVertex ][ i ] < INT_MAX)  //如果是可达的  
            prevVertex[ i ] = startVertex;    
        else    
            prevVertex[ i ] = -1;       //表示还不知道前一个节点是什么     
    }    
    prevVertex[ startVertex ] = -1;  //源节点无前一个节点
	distance[startVertex]=0;

	q.push(startVertex);
	isInQueue[startVertex] = true;
        
    for (int i = 1; i < numOfVertex; i ++)      //这里循环从1开始是因为开始节点已经存放在S中了,还有numOfVertex-1个节点要处理     
    {    
		//for(int start=0;start<numOfVertex;start++) Bellma-Ford中的使用所有的都循环,先在只循环那些最有可能达到最小解的
		//{
		 while(!q.empty()) 
		 {
			 int start = q.front();
			 cout<<"start"<<start<<endl;
			for (int j =0; j < numOfVertex; j ++)  
			{
				cout<<"start1"<<start<<endl;
				if (j!= startVertex  && map[start][j] < INT_MAX)  //在Q中,有距离的为c->d,c->e, c->b  
				{    
					int currentdist = distance[ start] + map[ start ][ j ];  
					if (currentdist < distance[ j ])  //distance[j = ]为开始到j的距离  
					{   
						distance[ j ] = currentdist;    
						prevVertex[ j ] = start;  
						if(!isInQueue[j])
						{
							q.push(j);
							isInQueue[j]= true;
						}
					}    
				}    
			}
			q.pop();
			isInQueue[start]= false;
		}
    } 
	//判断是否存在环
	bool isLoopExist = false;
	for(int start=0;start<numOfVertex&& !isLoopExist;start++)
	{		
		for (int j =0; j < numOfVertex && !isLoopExist; j ++)  
		{    
            if (j!= startVertex  && map[start][j] < INT_MAX)  //在Q中,有距离的为c->d,c->e, c->b  
            {    
                int currentdist = distance[ start] + map[ start ][ j ];    
                if (currentdist < distance[ j ])  //distance[j = ]为开始到j的距离  
                {    
					isLoopExist = true;
                }    
			}    
		} 
	}
			if(!isLoopExist)
		{
			cout<<"no loop"<<endl;
		}
}





你可能感兴趣的:(dijkstra,单源最短路径,Bellman-Ford)