单源最短路径之Bellman-Ford算法

原创 2016年06月01日 13:19:03

今天介绍一种计算单源最短路径的算法Bellman-Ford算法,对于图G=(V,E)来说,该算法的时间复杂度为O(VE),其中V是顶点数,E是边数。Bellman-Ford算法适用于任何有向图,并能报告图中存在负环路(边的权重之和为负数的环路,这使得图中所有经过该环路的路径的长度都可以通过反复行走该环路而使路径长度变小,即没有最短路径)的情况。以后会介绍运行速度更快,但只适用于没有负权重边的图中的Dijkstra算法。Dijkstra算法可以参考我的下一篇博客 单源最短路径之Dijkstra算法

在介绍Bellman-Ford算法之前,先介绍在计算图的单源最短路径的各种算法中都会用到的松弛操作。

// 松弛操作,检查<s, ..., v>的距离是否比<s, ..., u, v>大,是则更新<s, ..., v>为<s, ..., u, v>
void relax(Vertex *u, Vertex *v, int w)
{
	if (u->weight == INF || w == INF)	return;
	if (v->weight > u->weight + w)
	{
		v->weight = u->weight + w;
		v->p = u;
	}
}
Vertex是顶点的数据类型,<u,v>是图G中的一条边。顶点Vertex的属性weight记录了该顶点当前距离源点的最短距离,p记录了顶点在其最短距离中的前一个顶点。松弛操作要做的工作就是检查路径<s,...,v>的距离是否比<s,...,u,v>大,是则更新之,并把s到v的距离修改为s到u的距离加上<u,v>的长度,其中<s,...,v>为源点s到顶点v的原来的路径,<s,...,u>为源点s到顶点u的路径。

Bellman-Ford算法的思想就是反复对图G中的边<u,v>进行松弛操作,知道所有顶点到s的距离都被最小化为止。这里的图使用邻接表表示,下面给出图的定义和算法程序,Bellman-Ford算法需要的参数包括图g、权重矩阵w和源点编号s(顶点编号从1开始)。

typedef struct GNode
{
	int number;	// 顶点编号
	struct GNode *next;
} GNode;

typedef struct Vertex
{
	int number;
	int weight;		// 该顶点到源点的距离
	struct Vertex *p;
} Vertex;

typedef struct Graph
{
	GNode *LinkTable;
	Vertex *vertex;
	int VertexNum;
} Graph;

/**
* Bellman Ford 单源最短路径算法
* @return true 没有负环路; false 有负环路,最短路径构造失败
*/
bool Bellman_Ford(Graph *g, int **w, int s)
{
	initialize(g, s);

	GNode *linkTable = g->LinkTable;
	for (int i = 1; i < g->VertexNum; i++)
	{
		// 反复将边加入到已有的最小路径图中,检查是否有更优路径
		for (int j = 0; j < g->VertexNum; j++)
		{
			GNode *node = (linkTable + j)->next;
			Vertex *u = g->vertex + j;
			while (node != NULL)
			{
				Vertex *v = g->vertex + node->number - 1;
				int weight = *((int*)w + j * g->VertexNum + node->number - 1);
				relax(u, v, weight);
				node = node->next;
			}
		}
	}

	// 通过检查是否都已达到最短路径来检查是否存在负环路
	for (int j = 0; j < g->VertexNum; j++)
	{
		GNode *node = (linkTable + j)->next;
		Vertex *u = g->vertex + j;
		while (node != NULL)
		{
			Vertex *v = g->vertex + node->number - 1;
			int weight = *((int*)w + j * g->VertexNum + node->number - 1);
			if (v->weight > u->weight + weight)
			{
				return false;
			}
			node = node->next;
		}
	}
	return true;
}
void initialize(Graph *g, int s)
{
	Vertex *vs = g->vertex;
	for (int i = 0; i < g->VertexNum; i++)
	{
		Vertex *v = vs + i;
		v->p = NULL;
		v->weight = INF;
	}
	(vs + s - 1)->weight = 0;
}
上述算法代码实现的Bellman-Ford算法进行了V次对所有边的松弛操作,这是考虑到了最坏情况,假设图G是一条单链,则从表头s到表尾的路径计算需要进行V次松弛操作。下面给出一个演示例子。

	Graph graph;
	graph.VertexNum = 5;
	Vertex v[5];
	Vertex v1; v1.number = 1; v1.p = NULL; v[0] = v1;
	Vertex v2; v2.number = 2; v2.p = NULL; v[1] = v2;
	Vertex v3; v3.number = 3; v3.p = NULL; v[2] = v3;
	Vertex v4; v4.number = 4; v4.p = NULL; v[3] = v4;
	Vertex v5; v5.number = 5; v5.p = NULL; v[4] = v5;
	graph.vertex = v;

	GNode nodes[5];
	GNode n1; n1.number = 1;
	GNode n2; n2.number = 2;
	GNode n3; n3.number = 3;
	GNode n4; n4.number = 4;
	GNode n5; n5.number = 5;
	GNode a; a.number = 2; GNode b; b.number = 4; n1.next = &a; a.next = &b; b.next = NULL;
	GNode c; c.number = 3; GNode x; x.number = 4; GNode z; z.number = 5; n2.next = &c; c.next = &x; x.next = &z; z.next = NULL;
	GNode d; d.number = 2; n3.next = &d; d.next = NULL;
	GNode f; f.number = 5; GNode g; g.number = 3; n4.next = &f; f.next = &g; g.next = NULL;
	GNode h; h.number = 1; GNode i; i.number = 3; n5.next = &h; h.next = &i; i.next = NULL;
	nodes[0] = n1;
	nodes[1] = n2;
	nodes[2] = n3;
	nodes[3] = n4;
	nodes[4] = n5;
	graph.LinkTable = nodes;

	int w[5][5] = { 0,		6,			INF,		7,		INF,
					INF,	0,			5,			8,		-4,
					INF,	-2,			0,			INF,	INF,
					INF,	INF,		-3,			0,		9,
					2,		INF,		7,			INF,	0 };
	int s = 1;
	if (Bellman_Ford(&graph, (int **)w, s))
	{
		for (int i = 0; i < graph.VertexNum; i++)
		{
			if (i != s - 1)
			{
				Vertex *v = graph.vertex + i;
				printf("路径长度为%d , 路径为 : ", v->weight);
				while (v->p != NULL)
				{
					printf("%d <- ", v->number, v->p->number);
					v = v->p;
				}
				printf("%d\n", s);
			}
		}
	}
上面的例程构建的图如下图所示。

Bellman-Ford算法运行过程中各顶点v到源点s=1的距离变化如下所示。

0    INF    INF    INF    INF

0    6        4        7        2

0    2        4        7        2

0    2        4        7        -2

以顶点1到顶点2的路径变化为例,对应上面距离变化的顺序,如下所示。

无路径   --->   <1,2>   --->   <1,4,3,2>   --->   <1,4,3,2>

算法运行的最终结果如下图所示。

完整的程序可以看到我的github项目 数据结构与算法

这个项目里面有本博客介绍过的和没有介绍的以及将要介绍的《算法导论》中部分主要的数据结构和算法的C实现,有兴趣的可以fork或者star一下哦~ 由于本人还在研究《算法导论》,所以这个项目还会持续更新哦~ 大家一起好好学习~

版权声明:本文为博主原创文章,转载请注明原文地址

Bellman-Ford算法详讲

Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。 这时候,就需要使用其他的算法来求解最...

[算法系列之二十九]Bellman-Ford最短路径算法

单源最短路径给定一个图,和一个源顶点src,找到从src到其它所有所有顶点的最短路径,图中可能含有负权值的边。Dijksra的算法是一个贪婪算法,时间复杂度是O(VLogV)(使用最小堆)。但是迪杰斯...

Dijkstra、Bellman-Ford及Spfa算法思想对比

Dijkstradijkstra算法本质上算是贪心的思想,每次在剩余节点中找到离起点最近的节点放到队列中,并用来更新剩下的节点的距离,再将它标记上表示已经找到到它的最短路径,以后不用更新它了。这样做的...
  • mmy1996
  • mmy1996
  • 2016年08月16日 23:10
  • 4548

浅谈路径规划算法之Bellman-Ford算法

最近在研究AGV系统的调度算法,为了实现多AGV小车的运行,给每一个AGV小车规划一条最优路径,对比了Bellman-Ford算法、SPFA算法、Dijkstra算法、Floyd算法和A*算法的优缺点...
  • AK_Lady
  • AK_Lady
  • 2017年04月12日 20:35
  • 594

最短路径算法--Dijkstra算法,Bellmanford算法,Floyd算法,Johnson算法

大数据技术虫 最短路径算法 在交通地图上,两地点之间的路径通常标有长度,我们可以用加权有向来描述地图上的交通网。加权有向图中每条路径都有一个路径权值...

Bellman-Ford algorithm

Bellman-Ford算法能在一般的情况下解决
  • yeruby
  • yeruby
  • 2014年07月23日 21:51
  • 4547

关于 Bellman-Ford 与 Floyd 算法的一点感想

在四种常用的最短路算法 Dijkstra, SPFA, floyd, Bellman-Ford 中, Dijks 和 SPFA 的使用较为普遍, 对大多数人来说, 也较为熟悉. 然而, floyd 与...
  • KenxHe
  • KenxHe
  • 2016年11月18日 07:49
  • 763

算法导论——24.1 BellmanFord算法java实现

Bellman - ford算法是求含负权图的单源最短路径算法,效率很低,但代码很容易写。其原理为持续地进行松弛(原文是这么写的,为什么要叫松弛,争议很大),在每次松弛时把每条边都更新一下,若在n-1...
  • The_sam
  • The_sam
  • 2017年05月08日 21:43
  • 308

java实现图的最短路径(SP)的贝尔曼福特(Bellman-Ford)算法

/****************************************************************************** * Compilation: ja...

边上权值为任意值的单源最短路径问题——Bellman--Ford算法

Bellman—Ford算法
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:单源最短路径之Bellman-Ford算法
举报原因:
原因补充:

(最多只允许输入30个字)