SPFA算法O(kE)
主要思想是:
初始时将起点加入队列。每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若某个相邻的点修改成功,则将其入队。直到队列为空时算法结束。
这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法。
SPFA 在形式上和广度优先搜索非常类似,不同的是广度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是说一个点修改过其它的点之后,过了一段时间可能会获得更短的路径,于是再次用来修改其它的点,这样反复进行下去。
算法时间复杂度:O(kE),E是边数。K是常数,平均值为2。
算法实现:
dis[i]记录从起点s到i的最短路径,w[i][j]记录连接i,j的边的长度。pre[v]记录前趋。
team[1..n]为队列,头指针head,尾指针tail。
布尔数组exist[1..n]记录一个点是否现在存在在队列中。
初始化:d[s]=0,d[v]=∞(v≠s),memset(exist,false,sizeof(exist));
起点入队team[1]=s; head=0; tail=1;exist[s]=true;
do
{1、头指针向下移一位,取出指向的点u。
2、exist[u]=false;已被取出了队列
3、for与u相连的所有点v //注意不要去枚举所有点,用数组模拟邻接表存储
if (d[v]>d[u]+w[u][v])
{ d[v]=d[u]+w[u][v];
pre[v]=u;
if (!exist[v]) //队列中不存在v点,v入队。
{ //尾指针下移一位,v入队;
exist[v]=true;
}
}
}
while (head < tail);
循环队列:
采用循环队列能够降低队列大小,队列长度只需开到2*n+5即可。例题中的参考程序使用了循环队列。
1 #include<iostream> 2 #include<cstdio> 3 #define N 2010 4 #include<cstring> 5 using namespace std; 6 int dis[N]; //起点到其他点的最短路径 7 int pre[N]; //前驱 8 int map[N][N]; //两点之间距离 9 int ans[N]; //输出 10 int team[N]; //队列 11 bool pd[N]; //判断是否在队列中 12 int head,tail,n,m,from,to; 13 void work(int a) 14 { 15 team[tail++]=a; 16 pre[a]=a; 17 dis[a]=0; 18 pd[a]=1; 19 while(head<tail) 20 { 21 int d=team[head]; //取出队首元素 22 for(int i=1;i<=n;++i) 23 if(map[d][i]!=0&&dis[i]>dis[d]+map[d][i]) 24 { 25 dis[i]=dis[d]+map[d][i]; 26 pre[i]=d; 27 if(!pd[i]) 28 { 29 team[++tail]=i; 30 pd[i]=1; 31 } 32 } 33 head++; 34 pd[d]=0; 35 } 36 printf("%d ",dis[to]); 37 } 38 void print(int a,int b) 39 { 40 ans[1]=to; 41 int top=1; 42 int t=b; 43 while(t!=from) 44 { 45 t=pre[t]; 46 ans[++top]=t; 47 } 48 for(int i=top;i>=2;--i) 49 printf("%d->",ans[i]); 50 printf("%d",ans[1]); 51 } 52 int main() 53 { 54 memset(dis,0x7f,sizeof(dis)); //初始化 55 cin>>n>>m; 56 for(int i=1;i<=m;++i) 57 { 58 int x,y,q; 59 scanf("%d%d%d",&x,&y,&q); 60 map[x][y]=q; //有向图 61 } 62 cin>>from>>to; //需要计算的两点 63 work(from); 64 print(from,to); 65 return 0; 66 }