最短路之bellman-ford

针对dijkstra不能解决负权边而有的时间复杂度比较高的算法。

单源最短路径,时间复杂度为On^3

要求:不能存在负权回路,但是可以反映出这一情况。

运用松弛技术,每个顶点v属于V,逐步减小,源Sv的最短路径的估计值d[v],直至其

达到最短路径的权(s,v),...


Bellman-ford(G,w,s)

   INITIALIZE-SINGLE-SOURCE(G,s)

   FOR  i <-- to |V(G)-1|

          do for each edge(u,v) 属于 E(G)

               do relax (u,v,w)

   for each edge(u,v) 属于 E[G]

       do if d[v]>d[u]+w[u,v]

         then return false

   return true

dijkstra的区别是bellman没用贪心,而是暴力不断迭代,所以其时间规模较dijkstra有点大。

递推:


dist(1)[u]=edge[v0][u]

dist(k)[u]=min{dist(k-1)[u],min{dist(k-1)[j]+edge[j][u]}}

可优化为:


dist(0)[v]=inf,dist(0)[v0]=0

dist(k)[v]=min{dist(k-1)[v],dist(k-1)[u]+w[u,v]}


dist(1)[u]表示从原点v0到u的只经过一条边的最短路径长度

dist(n-1)[u]表示从原点v0到u(不形成回路)经过n-1条边的最短路径长度


递推实现代码:

#include<iostream>
#include <cstdio>
#define inf 1000000
#define maxn 8
int n;
int Edge[maxn][maxn];
int dist[maxn];
int pre[maxn];
using namespace std;
bool bellman(int v0)
{
	int i,j,k,u,temp;
	for(i=0;i<n;i++)
	{
		dist[i]=Edge[v0][i];
		if(i!=v0&&dist[i]<inf)
			pre[i]=v0;
		else 
			pre[i]=-1;
	}
	for(k=2;k<n;k++)
	{
		for(u=0;u<n;u++)
		{
			if(u!=v0)
			{
				for(j=0;j<n;j++)
				{
					if(Edge[j][u]<inf&&dist[j]+Edge[j][u]<dist[u])
					{
						dist[u]=dist[j]+Edge[j][u];
						pre[u]=j;
					}
				}
			}
		}
	}
	for(i=0;i<n;i++)
	{
		for(j=0;j<n;j++)
		{
			if(Edge[i][j]<inf&&dist[i]+Edge[i][j]<dist[j]) return false;
		}
	}
	return true;
}
int main()
{
	bool flag;
	int i,j;
	int u,v,w;
	scanf("%d",&n);
	while(scanf("%d%d%d",&u,&v,&w))
	{
		if(u==-1&&v==-1&&w==-1) break;
		Edge[u][v]=w;
	}
	for(i=0;i<n;i++)
	{
		for(j=0;j<n;j++)
		{
			if(i==j) Edge[i][j]=0;
			else if(Edge[i][j]==0) Edge[i][j]=inf;
		}
	}
	flag=bellman(0);
	if(flag)
	{
		int shortest[maxn];
		for(i=1;i<n;i++)
		{
			printf("%d\t",dist[i]);
			memset(shortest,0,sizeof(shortest));
			int k=0;
			shortest[k]=i;
			while(pre[shortest[k]]!=0)
			{
				k++;shortest[k]=pre[shortest[k-1]];
			}
			k++;shortest[k]=0;
			for(j=k;j>0;j--)
				printf("%d->",shortest[j]);
			printf("%d\n",shortest[0]);
		}
	}
	else 
		printf("error\n");
	return 0;
}
/*
7
0 1 6
0 2 5
0 3 5
1 4 -1
2 1 -2
2 4 1
3 2 -2
3 5 -1
4 6 3
5 6 3
-1 -1 -1

*/

在此基础上若用dijkstra求出上界,则会加速bellman-ford的迭代过程:
//即先用dijkstra求一遍。因为存在负权边,所以求出来的不一定是最短边,但其上界就大大缩小了。再用bellman-ford求解速度加快。但这样做也并没有改变其时间复杂度。
改过的代码:
bool bellman(int v0)
{
	int i,j,k,u,temp,S[maxn];
	for(i=0;i<n;i++)
	{
		S[i]=0;
		dist[i]=Edge[v0][i];
		if(i!=v0&&dist[i]<inf)
			pre[i]=v0;
		else 
			pre[i]=-1;
	}
	S[v0]=1;
	for(i=0;i<n;i++)
	{
		u=v0,temp=inf;
		for(j=0;j<n;j++)
		{
			if(!S[j]&&dist[j]<temp)
				u=j,temp=dist[j];
		}
		S[u]=1;
		for(k=0;k<n;k++)
		{
			if(!S[k]&&Edge[u][k]&&dist[k]>dist[u]+Edge[u][k])
				dist[k]=dist[u]+Edge[u][k],pre[k]=u;
		}
	}
	for(k=2;k<n;k++)
	{
		for(u=0;u<n;u++)
		{
			if(u!=v0)
			{
				for(j=0;j<n;j++)
				{
					if(Edge[j][u]<inf&&dist[j]+Edge[j][u]<dist[u])
					{
						dist[u]=dist[j]+Edge[j][u];
						pre[u]=j;
					}
				}
			}
		}
	}
	for(i=0;i<n;i++)
	{
		for(j=0;j<n;j++)
		{
			if(Edge[i][j]<inf&&dist[i]+Edge[i][j]<dist[j]) return false;
		}
	}
	return true;
}



若采用邻接表做bellman-ford时间复杂度将下降为O(n*m)


bool bellman(int v0)
{
	int i,k;
	for(i=0;i<n;i++)
	{
		dist[i]=inf;
		pre[i]=-1;
	}
	dist[v0]=0;
	for(k=2;k<n;k++)
	{
		for(i=0;i<n;i++)
		{
			if(dist[Edge[i].u]!=inf&&Edge[i].w+dist[Edge[i].u]<dist[Edge[i].v])
			{
				dist[i].v=Edge[i].w+dist[Edge[i].u];
				pre[Edge[i].v]=Edge[i].u;
			}
		}
	}
	for(i=0;i<m;i++)
	{
		if(dist[Edge[i].u]!=inf&&Edge[i].w+dist[Edge[i].u]<dist[Edge[i].v])
			return false;
	}
	return true;
}



bellman-ford还是用邻接表比较好,虽然存储是有些麻烦。

//

SPFA:

bellman-ford的队列实现,减少了不必要的冗余判断。时间复杂度为O(k*m),k为每个顶点入队的平均次数,对于通常的情况k==2.

初始时讲源点加入队列Q ,每次从队列中取出一个顶点,并对所有与他相邻的顶点进行松弛操作,若松弛成功则将其入队(改变过的u),重复操作,直至队列为空。

SPFA(G,w,s)

INITALIZE-SINGLE-SOURCE(G,s)

INITLALIZE-QUEUE(Q)

ENQUEUE(Q,s)

WHILE   Q != NULL

        do u <-- DLQUEUE(Q)

        do tmp <--  d[v]

        relax(u,v,w)

       if(d[v]<tmp and v not in Q)

       ENQUEUE(Q,v)


SPFA 算法和bfs有一定的相似性,不同的是bfs搜索中一个顶点出了队列就不可能再次被放入队列,但是spfa在一个顶点可能在出了队列之后再次被放入队列,也就是说一个顶点改进过其他顶点之后,过了一段时间后可能本身会被改进,于是再次用来改进其他顶点,这样反复迭代下去。



#include <cstdio>
#include <iostream>
#include <sstream>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <stack>
#include <queue> 
#include <set>
#include <map>
#include <algorithm>
#define inf 1000000
#define maxn 10
using  namespace  std;
struct ArcNode{
	int to;
	int weight;
	ArcNode *next;
};
queue <int >Q;//队列中的节点为顶点序号。
int n;
ArcNode *list[maxn];//每个顶点的边链表表头指针
int inq[maxn];
int dist[maxn];
int pre[maxn];
void SPFA(int v0)
{
	int i,u;
	ArcNode *temp;
	for(i=0;i<n;i++)
	{
		dist[i]=inf;pre[i]=v0;inq[i]=0;
	}
	dist[v0]=0;pre[v0]=v0;inq[v0]++;
	Q.push(v0);
	while(!Q.empty ())
	{
		u=Q.front ();
		Q.pop ();
		inq[u]--;
		temp=list[u];
		while(temp!=NULL)
		{
			int v=temp->to;
			if(dist[v]>dist[u]+temp->weight)
			{
				dist[v]=dist[u]+temp->weight;
				pre[v]=u;
				if(!inq[v]) {Q.push (v);inq[v]++;}
			}
			temp=temp->next;
		}
	}
}
int main()
{
	int i,j;
	int u,v,w;
	scanf("%d",&n);
	memset(list,0,sizeof(list));
	ArcNode *temp;
	while(~scanf("%d%d%d",&u,&v,&w))
	{
		if(u==-1&&v==-1&&w==-1) break;
		temp=new ArcNode;
		temp->to=v;
		temp->weight=w;
		temp->next=NULL;
		if(list[u]==NULL) list[u]=temp;
		else 
		{
			temp->next=list[u];
			list[u]=temp;
		}
	}
	SPFA(0);
	for(j=0;j<n;j++)//释放空间
	{
		temp=list[j];
		while(temp!=NULL)
		{
			list[j]=temp->next;delete temp;temp=list[j];
		}
	}
	int shortest[maxn];
	for(i=1;i<n;i++)
	{
		printf("%d\t",dist[i]);
		memset(shortest,0,sizeof(shortest));
		int k=0;
		shortest[k]=i;
		while(pre[shortest[k]]!=0)
		{
			k++;
			shortest[k]=pre[shortest[k-1]];
		}
		k++;
		shortest[k]=0;
		for(j=k;j>0;j--)
			printf("%d-->",shortest[j]);
		printf("%d\n",shortest[0]);
	}
	return 0;
}
/*
7
0 1 6
0 2 5
0 3 5
1 4 -1
2 1 -2
2 4 1
3 2 -2
3 5 -1
4 6 3
5 6 3
-1 -1 -1

*/


你可能感兴趣的:(最短路之bellman-ford)