【算法】最短路径之SPFA

1.算法描述


SPFA(Shortest Path Faster Algorithm)算法是西南交通大学的段凡丁于1994提出,是队列优化版的Bellman-Ford。将源点加入队列;每次从队列出来一个点,对相邻的点,进行松弛操作;被松弛成功且不在队列的点依次进入队列;重复操作,直至队列为空算法结束。


dis[u]表示源点到点u的最短距离;用数组queue[ ]模拟队列,head, rear分别表示队首、队尾,dequeue表示出队的元素;within[u]标记节点u在队列中。


算法流程:

(1)初始化:将queue元素全部置为-1,源点进入队列queue。

(2)队首出队,dequeue=queue[head]; head++; 松弛与dequeue相邻的节点v:dis[v]=min{dis[v], dis[dequeue]+(dequeue,v)};

dis[v]<dis[dequeue]+(dequeue,v),即标明松弛成功,v如果不在队列中则入队;将dequeue标记为已出队。

(3)判断队列为空:dequeue==-1,若不为空,重复操作(2)。


用邻接表(adjacency list)作为图的存储结构,SPFA算法时间复杂度为O(ke)。


2. 图的邻接表表示


在邻接表中,对每个顶点u建立一个单链表。单链表由c(u)个表结点组成,c(u)表示节点u的出度;每一个单链表都有一个表头结点。


2.1 表结点


--------------------------------
| adjvex | weight | next |   
--------------------------------

adjvex表示与u相邻节点,weight表示边的权值,next指向u的另一个相邻节点。

2.2 头结点


------------------------
| vertex | firstarc |   
------------------------

vertex表示顶点u,firstarc指向依附于u的第一条边。

3. Referrence


[1] NOCOW, http://www.nocow.cn/index.php/SPFA算法.
[2] 段凡丁,关于最短路径的SPFA快速算法,西南交通大学学报,1994.
[3] xw13106209, 有向图的邻接表表示法.

4.问题


4.1 POJ 1511


round trip是从源点s出发,到顶点u,再回到源点s。求解:有向图(n,m)中,n-1个顶点的round trip之和的最小值。

先求源点s到每个顶点的最短路径,然后相加求和;将边反向建立图,求s到每个顶点的最短路径,然后相加求和;将两次的和相加即得最终结果。

在结构题内定义指针要加上struct,比如:typedef struct ArcNode {…… ArcNode *next; } ArcNode 是错误的,应该是typedef struct ArcNode {…… struct ArcNode *next; } ArcNode ;因此有两次Compile Error 

定义dis[ ]要用__int64或long long,inf 要超过1000000000。自己没注意到,有两次WA。

源代码:

1511 Accepted 109912K 6563MS C 2651B 2013-08-16 16:33:55
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

#define MAXN 1000000
#define inf 1100000000

__int64 sum;

/*表结点*/
typedef struct ArcNode
{
	int adjvex, weight;
    struct ArcNode *next;
}ArcNode;

ArcNode *pnew, *rpnew;

/*头结点*/
typedef struct VNode 
{
	int vertex;
	struct ArcNode *firstarc;
}VNode;

/*graph represented by adjacency list*/
typedef struct
{
	int n,m;
	VNode GVertex[MAXN];
}ALGraph;

/*create a graph & a reverse graph by using adjacency-list*/
void CreateGraph(ALGraph *graph,ALGraph *reverse)
{
	int i,n,m;
	int start,end,wei;
	scanf("%d%d",&n,&m);
	graph->n=reverse->n=n;
	graph->m=reverse->m=m;
	
	for(i=0;i<n;i++)
	{
		graph->GVertex[i].vertex=i;
		graph->GVertex[i].firstarc=NULL;
		reverse->GVertex[i].vertex=i;
		reverse->GVertex[i].firstarc=NULL;
	}
	
	for(i=0;i<m;i++)
	{
		scanf("%d%d%d",&start,&end,&wei);
		pnew=(ArcNode *) malloc(sizeof(ArcNode));
		pnew->adjvex=end-1;
		pnew->weight=wei;
		pnew->next=graph->GVertex[start-1].firstarc;
        graph->GVertex[start-1].firstarc=pnew;
		
		rpnew=(ArcNode *) malloc(sizeof(ArcNode));
		rpnew->adjvex=start-1;
		rpnew->weight=wei;
		rpnew->next=reverse->GVertex[end-1].firstarc;
        reverse->GVertex[end-1].firstarc=rpnew;
	}
}

/*spfa algorithm, the source node is 0*/
void spfa(ALGraph *graph)
{
	const int num_ver=graph->n;
	int i;
    int head,rear,dequeue;
	int *within=(int *) malloc(num_ver*sizeof(int));
	int *queue=(int *) malloc((num_ver+1)*sizeof(int));
	__int64 *dis=(__int64 *)malloc(num_ver*sizeof(__int64));
	
	/*initialization*/
	memset(within,0,num_ver*sizeof(int));
	memset(queue,-1,(num_ver+1)*sizeof(int));
	for(i=0;i<num_ver;i++)
		dis[i]=inf;
	dis[0]=0;
	queue[0]=0;
	dequeue=head=rear=0;
	
	for(;dequeue!=-1;dequeue=queue[head],head++)
	{
		ArcNode *p=graph->GVertex[dequeue].firstarc;		
		while(p!=NULL)
		{
			if(dis[p->adjvex]>dis[dequeue]+p->weight)
			{
				dis[p->adjvex]=dis[dequeue]+p->weight;
				if(within[p->adjvex]==0)
				{
					within[p->adjvex]=1;
					queue[rear+1]=p->adjvex;
					rear++;
				}
			}
			p=p->next;
		}
	   within[dequeue]=0;
	}
	
	/*calculate the sum of the minimum shortest path*/ 
	for(i=0;i<num_ver;i++)
		sum+=dis[i];
	
	free(within);
	free(queue);
	free(dis);
}


int main()
{
	int test_case;
	ALGraph *graph=(ALGraph *) malloc(sizeof(ALGraph));
	ALGraph *reverse=(ALGraph *) malloc(sizeof(ALGraph));
	
	scanf("%d",&test_case);
	while(test_case--)
	{	
		CreateGraph(graph,reverse);
		sum=0;
		spfa(graph);
		spfa(reverse);
		printf("%I64d\n",sum);
	}
	return 0;
}

4.2 POJ 3037


题目大意:有一个R*C(即R row, C coloumn)的网格,网格中每一点(即location)对应一个速度,速度跟该点的height(elevation)相关,且满足:如果从a移到b,speed[b]=speed[a]*2^(elevation[a]-elevation[b]) ;求从左上角移到右下角的最短路径。


(1)Titanium指出这个题的巧妙之处:无论怎么走,从起点走到任何一个点,到达的那个点的速度都是固定的。

对于线路a--->b---->c ,c点速度为 speed[c]=speed[b]*2^(elevation[b]-elevation[c]) =speed[a]*2^(elevation[a]-elevation[c])  。

(2)从一个位置a移到其邻接位置(adjacent location)的距离是1.0,所花费的时间为1.0/speed[a] 。

(3)因为只能东南西北的移动,所以网格隐含着一个邻接表。

(4)这道题可以用BFS+Priority_queue优化求解,具体请参看popopopolo。


函数pow(,)内的两个参数类型因保持一致,因pow(2,)而Compile Error一次,应改成pow(2.0,) 。


源代码(C++):


3037 Accepted 524K 313MS C++ 1787B 2013-08-16 22:36:29
#include <iostream>
#include <cmath>
#include <queue>
using namespace std;

#define MAXR 100
#define MAXC 100
#define inf 11000000000

/*describe a location*/
struct location
{
	int x,y;
};

/*represent the west, north, east, south*/
const location direction[4]={{-1,0},{0,-1},{1,0},{0,1}};

int init_speed,row,coloumn;
double speed[MAXR][MAXC],dis[MAXR][MAXC];
int elevation[MAXR][MAXC],within[MAXR][MAXC];

/*input and get the speed at each location*/
void get_speed()
{
    int i,j;
	cin>>init_speed>>row>>coloumn;
	speed[0][0]=init_speed;
    for(i=0;i<row;i++)
		for(j=0;j<coloumn;j++)
		{
			cin>>elevation[i][j];
            speed[i][j]=init_speed*pow(2.0,elevation[0][0]-elevation[i][j]);
		}
}

void spfa( )
{
	queue<location>Que;
	location dequeue,enqueue;
	int i,j;
	
	/*initialization*/
	for(i=0;i<row;i++)
	{
		for(j=0;j<coloumn;j++)
		{
			dis[i][j]=inf;
			within[i][j]=0;
		}
	}
	dequeue.x=0; dequeue.y=0;
	dis[0][0]=0; within[0][0]=1;
	Que.push(dequeue);
	
	while(!Que.empty())
	{
		int xadj,yadj;
		dequeue=Que.front();
		Que.pop();
		within[dequeue.x][dequeue.y]=0;
		
		for(i=0;i<4;i++)
		{
			xadj=dequeue.x+direction[i].x;  //adjacent location
			yadj=dequeue.y+direction[i].y;
			if(xadj>=0&&xadj<row&&yadj>=0&&yadj<coloumn)
			{
				double time=1.0/speed[dequeue.x][dequeue.y];
				if(dis[xadj][yadj]>dis[dequeue.x][dequeue.y]+time)    //relax
				{
					dis[xadj][yadj]=dis[dequeue.x][dequeue.y]+time;
					if(within[xadj][yadj]==0)
					{
						within[xadj][yadj]=1;
						enqueue.x=xadj;
						enqueue.y=yadj;
						Que.push(enqueue);     //enqueue
					}
				}
			}
		}
	}
}

int main()
{
	get_speed();
	spfa();
    printf("%.2f\n",dis[row-1][coloumn-1]);
	return 0;
}

4.3 POJ 3159


题目大意:B的糖果比A至多只能多出c(A, B)个,求编号为N的flymouse比编号为1的snoopy至多多出多少个糖果。

这是一道差分约束题,可以转化为求最短路径的问题。用dis[v]表示点v比源点至多多出糖果数,则有dis[B]-dis[A]>=c(A, B) ;使用松弛:dis[B]=min{dis[B], dis[A]+c(A, B)} 。因此,变成了求源点到点N的最短路径问题。

Discuss中讨论说用queue会TLE,建议用stack。我用stack后,还是TLE。然后参考Wingszero ,将邻接表用边表+firstarc数组所替代,结果还是TLE。最后将cin换成了scanf、cout换成了printf,才AC了。


源代码:


3159 Accepted 2128K 672MS C++ 1480B 2013-08-21 20:18:24

#include <iostream>
#include <stack>
using namespace std;

#define MAXN 30001
#define MAXM 150001
#define inf INT_MAX

struct EDGE
{
	int v,weight;
	int next;
};

EDGE edge[MAXM];
int firstarc[MAXN];

/*create a graph by using an adjacency list*/
void CreatGraph(int n, int m)
{
	int i,position=1;
	int start,end,wei;
	memset(firstarc,0,sizeof(firstarc));
	
	for(i=0;i<m;i++)
	{
		scanf("%d%d%d",&start,&end,&wei);
		edge[position].v=end;
		edge[position].weight=wei;
		edge[position].next=firstarc[start];
		firstarc[start]=position++;
	}
	
}

void spfa(int n, int m)
{
	stack<int>spfa_stack;
	int i, poping;           //poping represents the poping element of the stack
	int *dis=new int [n];
	int *within=new int [n];
	
	/*initialization*/
	fill(dis,dis+n+1,inf);
	memset(within,0,(n+1)*sizeof(int));
	dis[1]=0;
	within[1]=1;
	spfa_stack.push(1);
	
	while(!spfa_stack.empty())
	{
		poping=spfa_stack.top();
		spfa_stack.pop();
		within[poping]=0;
		for(i=firstarc[poping];i;i=edge[i].next)
		{
			if(dis[edge[i].v]>dis[poping]+edge[i].weight)    //relax
			{
				dis[edge[i].v]=dis[poping]+edge[i].weight;
				if(!within[edge[i].v])                 // push stack
				{
					spfa_stack.push(edge[i].v);
					within[edge[i].v]=1;
				}
			}
		}
	}
	
	printf("%d\n",dis[n]);
	delete []dis;
	delete []within;
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	CreatGraph(n,m);
	spfa(n,m);
	return 0;
}


你可能感兴趣的:(【算法】最短路径之SPFA)