【算法】最短路径之Bellman-Ford

1.算法描述


Bellman-Ford算法是由Richard Bellman和Lester Ford所提出的,用于求解单源最短路径。与Dijkstra算法所不同的是,边的权值可为负,算法复杂度O(VE)。

dis[i]记录源点到点i的最短距离,V表示顶点个数,E表示边的条数。

算法具体步骤如下:
(1)对每一条边(u,v)进行松弛操作:dis[v]=min{dis[v], dis[u]+edge[u][v]} ,每一次的松弛操作是对相邻节点的访问;
(2)循环重复松弛操作V-1次;
(3)检查负权环:对边(u,v),如果存在dis[v]>dis[u]+edge[u][v],则负权环存在。

优化:如果dis[]没有更新,则后续的循环重复中也不会更新,因而只要dis[]无更新则退出循环。

2.Referrence


[1] Wikipedia, http://zh.wikipedia.org/zh-cn/贝尔曼-福特算法

[2] Tanky Woo, 最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)


3.问题


3.1 POJ 3259


题目大意:有F个farm,每个farm由N个field组成;field之间有双向的path,也有单向并且是负权的wormhole;现在求解是否存在负权环。

再次栽在标号处理的问题:数组dis[ ]的标号与每个field的标号相差1。

源代码:

3259 Accepted 224K 63MS C 1699B 2013-08-14 11:17:26

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 5200
#define inf 0xffff

typedef struct 
{
    int start,end;
    int weight;
}EDGE;

EDGE path [MAX];
int num_path;

void init(int M,int W)
{
    int i,st,en,wei;
    num_path=0;
	
    for(i=0;i<M;i++)
    {
        scanf("%d%d%d",&st,&en,&wei);
        path[num_path].start=st;
        path[num_path].end=en;
        path[num_path].weight=wei;
        num_path++;
        path[num_path].start=en;
        path[num_path].end=st;
        path[num_path].weight=wei;
        num_path++;
    }
	
    for(i=0;i<W;i++)
    {
        scanf("%d%d%d",&st,&en,&wei);
		path[num_path].start=st;
		path[num_path].end=en;
		path[num_path].weight=-wei;
        num_path++;
    }
}

int bellman_ford(int N)
{
    int i,j,negative_flag=0,relax_flag;
    int *dis=(int*) malloc((N+1)*sizeof(int));

    /*initialize dis*/	
    for(i=1;i<N;i++)
        dis[i]=inf;
    dis[1]=0;

	/*relax*/
    for(j=1;j<N;j++)
	{
		relax_flag=0;
        for(i=0;i<num_path;i++)
            if(dis[path[i].start]+path[i].weight<dis[path[i].end])
            {
                dis[path[i].end]=dis[path[i].start]+path[i].weight;
				relax_flag=1;
            }
		if(!relax_flag)
			break;
	}
	
	/*check negative loop*/
    for(i=0;i<num_path;i++)
        if(dis[path[i].start]+path[i].weight<dis[path[i].end])
        {
            negative_flag=1;
            break;
        }
		
	free(dis);	
	return negative_flag;
}

int main()
{
	int num_farm,N,M,W;
	scanf("%d",&num_farm);
	while(num_farm--)
	{
		scanf("%d%d%d",&N,&M,&W);
		init(M,W);
		if(bellman_ford(N))
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}

3.2 POJ 2240


货币套现的问题,可以简化为:
(1)将每一种货币看作一个顶点,交易比率(exchange rate)作为一条有向边,建立有向图;
(2)dis[i]表示到源点source到节点i的最大交易比率,修改松弛条件:dis[v]=max{dis[v], dis[u]*rate(u,v)} ;
(3)判断套现(arbitrage)是否可能,即判断dis[source]>1.0 。

边的数组开小了,贡献两次Runtime Error;输出少打了一个空格,贡献了一次Presentation Error

源代码:

2240 Accepted 196K 63MS C 1582B 2013-08-14 20:36:20

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXN 30
#define MAXM 1000

typedef struct 
{
    int start,end;
    double weight;
}EDGE;

EDGE edge[MAXM];
char currencies[MAXN][20];
int N,M,arbitrage_flag;

int map(char str[20],int N)
{
	int i;
	for(i=0;i<N;i++)
		if(!strcmp(str,currencies[i]))
			return i;
	return -1;	
}

void init()
{
    int i;
	char str1[20],str2[20];
	double rate;
	for(i=0;i<N;i++)
		scanf("%s",currencies[i]);
    scanf("%d",&M);
	for(i=0;i<M;i++)
	{
		scanf("%s%lf%s",str1,&rate,str2);
		edge[i].start=map(str1,N);
		edge[i].end=map(str2,N);
		edge[i].weight=rate;
	}  
}

void bellman_ford(int source)
{
    int i,j;
    double *dis=(double*) malloc(N*sizeof(double));
	arbitrage_flag=0;
	
    /*initialize dis*/	
    memset(dis,0,N*sizeof(double));
    dis[source]=1.0;
	
	/*relax*/
    for(j=1;j<=N;j++)
	{
        for(i=0;i<M;i++)
            if(dis[edge[i].start]*edge[i].weight>dis[edge[i].end])
                dis[edge[i].end]=dis[edge[i].start]*edge[i].weight;
	}
	
	/*check whether arbitrage is possible*/
    if(dis[source]>1.0)
		arbitrage_flag=1;
		
	free(dis);	
}

int main()
{
	int i,test_case=1;
	while(scanf("%d",&N)!=EOF&&N)
	{
		init();
		for(i=0;i<N;i++)
		{
			bellman_ford(i);
			if(arbitrage_flag)
				break;
		}
		if(arbitrage_flag)
			printf("%s%d%s\n","Case ",test_case,": Yes");
		else
			printf("%s%d%s\n","Case ",test_case,": No");
		test_case++;
	}
	return 0;
}

3.3 POJ 1860


与POJ 2240不同的是,要减去commission。

参考 Discuss jwzxgo的思路, 修改松弛条件:dis[v]=max{dis[v], (dis[u]-commission(u,v))*rate(u,v)} ;
判断negative sum:dis[v] < (dis[u]-commission(u,v))*rate(u,v) 。

数组point开了100,贡献了一次Runtime Error,将MAX改成200就AC了。

源代码:

1860 Accepted 188K 16MS C 1343B 2013-08-14 21:57:07

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 200

typedef struct 
{
    int start,end;
    double rate,commission;
}EXCHANGE;

EXCHANGE point[MAX];
int N,M,S;
double V;

void init()
{
    int i,a,b;
	scanf("%d%d%d%lf",&N,&M,&S,&V);
	for(i=0;i<M;i++)
	{
		scanf("%d%d",&a,&b);
        point[i].start=a-1; 
		point[i].end=b-1;
		scanf("%lf%lf",&point[i].rate,&point[i].commission);
		point[M+i].start=b-1; 
		point[M+i].end=a-1;
		scanf("%lf%lf",&point[M+i].rate,&point[M+i].commission);
	}
}

/*return larger number*/
double larger(double be,double af)
{
	return be>=af?be:af;
}

int bellman_ford( )
{
    int i,j,negative_flag=0;
    double *dis=(double*) malloc(N*sizeof(double));
	
    /*initialize dis*/	
    memset(dis,0,N*sizeof(double));
    dis[S-1]=V;
	
	/*relax*/
    for(j=1;j<=N;j++)
        for(i=0;i<2*M;i++)
            dis[point[i].end]=larger(dis[point[i].end],(dis[point[i].start]-point[i].commission)*point[i].rate);
	
	/*check negative loop*/
    for(i=0;i<2*M;i++)
		if(dis[point[i].end]<(dis[point[i].start]-point[i].commission)*point[i].rate)
		{
			negative_flag=1;
			break;
		}
		
	free(dis);
	return negative_flag;
}

int main()
{
	init();
	if(bellman_ford())
		printf("YES\n");
	else
		printf("NO\n");
	
	return 0;
}

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