【暑假集训笔记】最短路算法_Bellman-Ford算法+SPFA算法(bellman的队列优化)+路径还原

/*Bellman-Ford: 
	作用: 单源最短路径;判断环路 
	条件: 
	有向图&无向图; 
	边权可正可负 

	实现过程的三步:
	1、初始化所有的点,每一个点保存一个值,表示源点到这个点的距离其他点的值设为无穷大。 
	2、进行循环,从1到n-1,进行松弛计算。 
	3、遍历所有边,如果的d[v]大于d[u]+w(u,v)存在,则有从源点可达的权为负的回路。
*/ 

int u[N],v[N],w[N];//u->v == w
int dis[N]; //存储距离
int n,m;//n个点,m个边 
//初始化
for(int i=1;i<=n;i++){
	dis[i] = inf; 
} 
//假设求1节点的最短路径
dis[1] = 0;

for(int k=1;k<=n-1;k++){
	for(int i=1;i<=m;i++){
		dis[ v[i] ] = min(dis[ v[i] ],dis[ u[i] ]+w[i])//终点 与起点加权值相比较 
	}
} 


//微微优化+判断回路
//引入备份数组
int bak[N];
int u[N],v[N],w[N];//u->v == w
int dis[N]; //存储距离
int n,m;//n个点,m个边 
//初始化
for(int i=1;i<=n;i++){
	dis[i] = inf; 
} 
//假设求1节点的最短路径
dis[1] = 0;

for(int k=1;k<=n-1;k++){
	//在这里 :
	for(int i=1;i<=n;i++) bak[i] = dis[i]; 
	for(int i=1;i<=m;i++){
		dis[ v[i] ] = min(dis[ v[i] ],dis[ u[i] ]+w[i])//终点 与起点加权值相比较 
	}
	bool check=false;
	for(int i=1;i<=n;i++){
		if(bak[i]!=dis[i]){
			check = true;
			break;
		}
	}
	if(!check) break;
}

//检测负权回路
bool flag = false;
for(int i=1;i<=m;i++){
	if(dis[ v[i] ]>dis[ u[i] ]+w[i]){
		flag=true;
		break;
	}
} 
if(flag){
	cout<<"有回路"<<endl; 
}

//SPFA——基于Bellman-Ford的队列优化
/*
Bellman算法在每一次实施松弛操作时,就会有一些顶点已经求得最短路径,此后这些顶点的最短路径的估计值就会一直保持不变;
不再受后续松弛操作的影响,但是每次还要判断是否需要松弛,这里浪费了大量的时间.
SPFA(Shortest Path Faster Algorithm)是基于Bellman-Ford算法的改进,每次进队最短路径估计值发生变化了的顶点的所有出边执行松弛操作,借助一个队列.


思想:
每次选取队首顶点u,对顶点u的所有出边进行松弛操作。
例如有一条u->v的边,如果通过u->v的这条边使得源点到顶点v的最短路径变得更短,也即(dis[u] + e[u][v] < dis[v]),
并且顶点v不在当前的队列(并用借助一个数组标记顶点是否已经在队列之中),则将顶点v放置队尾. 
对顶点u的所有出边松弛完毕之后,就将顶点u出队,接下来不断从队列中取出新的队首顶点反复上面操作直到队列为空结束.

用队列优化的Bellman-Ford算法的关键之处:
只有那些在前一遍松弛中改变了最短路径估计值的点,才可能引起它们邻接点最短路径估计值发生改变. 
因此用一个队列保存被成成功松弛的顶点,之后只对队列中的点进行处理,这就降低了算法的时间复杂度.


Q:SPFA如何判断一个图是否有负环?
如果某个点进入队列次数超过n次,那么这个图肯定存在负环.

*/ 
//邻接矩阵+模拟队列 实现
void spfa(int s){
	//初始化 vis dis maps
	dis[s]=0; vis[s]=1; q[1]=s;  //队列初始化,s为起点
	int i, v, head=0, tail=1;
	while (head<tail){   //队列非空
		head++; 
		v=q[head];  //取队首元素
		vis[v]=0;  // 释放队首结点,因为这节点可能下次用来松弛其它节点,重新入队
		for(i=0; i<=n; i++)  //对所有顶点
		   if (maps[v][i]>0 && dis[i]>dis[v]+maps[v][i]){  
				dis[i] = dis[v]+maps[v][i];   //修改最短路
				if (vis[i]==0){ // 如果扩展结点i不在队列中,入队
					tail++;
					q[tail]=i;
					vis[i]=1;
				}
		   }
		
	}
}
//邻接矩阵
void SPFA(int s)    
{    
    //初始化 vis dis maps
    vis[s]=true;    
    dis[s]=0;      
    queue<int> q;    
    q.push(s);    
    while(!q.empty())    
    {    
        int v=q.front();    
        q.pop();    
        vis[v]=false;    
        for(int i=0;i<n;i++)    
        {    
            if(dis[v] + maps[v][i] < dis[i])    
            {    
                dis[i]=dis[v] + maps[v][i];    
                if(!vis[i]){    
                    q.push(i);    
                    vis[i]=true;    
                }    
            }               
        }    
    }    
}
//邻接表实现
const int INF=0x3f3f3f3f;
const int N=210;
int n,m,s,t;
int dis[N],vis[N],sum[N];
struct node{
    int v; ///点
    int weight; ///权值
};
vector<node>maps[N]; //储存边;//借用邻接表的原理
void SPFA(int n)
{
    int q;
    queue<int> Q;
    vis[n] = 1;
    dis[n] = 0;
    Q.push(n);
    while(!Q.empty())
    {
        q = Q.front();
        Q.pop();
        vis[q] = 0;
        for(int i=0;i<maps[q].size();i++)
        {
			int v = maps[q][i].v;
            if(dis[q] + maps[q][i].weight < dis[v])
            {
                dis[v] = dis[q] + maps[q][i].weight;
                if(!vis[v])
                {
                    Q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    return ;
}
//思维模板
/*
(1) 初始化:dis[s]=0,dis[i]= +∞,新建一个队列,将源点s入队,标记s已经入队。

(2) 从队首取出一个点u,标记u已经出队,将与u有边相连的点v进行松弛,,
		如果松弛成功,判断v是否在队列中,如果没有入队,标记v入队。继续此步骤,直到队列为空。
*/
q.push(s);
vis[s]=1;
while(q.size())
{
	u=q.front();q.pop();
	vis[u]=0;       
	for(i=head[u];i;i=next[i])
	{
		v=ver[i];
		w=edge[i];
		if(dis[u]+w<dis[v])
		{
			dis[v]=dis[u]+w;
			if(!vis[v]){
				q.push(v);vis[v]=1;
			}    
		}    
	}
}

猜你喜欢

转载自blog.csdn.net/F_zmmfs/article/details/81711189