单源最短路径(bellman-ford 、dijkstra)

对一个带权有向图G=(V,E),给定一个源顶点S,找出S到图中其他顶点v的最短路径即单源最短路径问题。该问题还有很多变体,像单终点最短路径、单对顶点最短路径、每对顶点间的最短路径等等。

最短路径问题是具有最优子结构的:一对顶点间的最短路径包含了该路径上的顶点间的最短路径。直观上理解,如果该路径上的两个顶点间的路径pij不是最短路径,那么用这两个顶点间的最短路径代替pij,那么就会出现一条更短的路径,与前面所说的最短路径矛盾。(具体证明参见算法导论P358)。

需要说明的是负权值边和松弛技术。Dijkstra算法是不允许图中存在负权边的,否则无法得到正确的结果。而Bellman-ford算法就允许图中存在负权边,而且该算法可以检测图中是否存在负权回路。两种算法都用到了松弛技术。即对边(uv),如果通过u到达v比当前找到的到v的最短路径还短,那么就更新d[v]parent[v]。通过松弛,可以减小最短路径估计。

Bellman-ford算法:

因为图中任意两个顶点的最短路径最多包含|V|-1条边,所以至多对每条边进行|V|-1次松弛后就会得到任意两个顶点间的实际最短路径。如果还能通过松弛降低最短路径估计,那么就可以断定图中存在负权回路,因为如果从sv的路径中包含负权回路,那么sv的最短路径长度就是负无穷了。可以这样理解,第ii>=1)次松弛得到的是源点s到每个顶点vV的路径长度为i的最短路径,第|V|-1次松弛得到的就是长度为|V|-1的最短路径。不过,显然不是每个顶点到s的最短路径长度都是|V|-1,所以对每条边都进行|V|-1次松弛操作是没有必要的。Bellman-ford的时间复杂度为O(VE)。可以对该算法进行简单的优化,如果本次循环并未对任何一条边进行松弛,那么可以判定已经得到了最终结果,退出循环。

template< typename VertexType, typename CostType >
bool Graph<VertexType, CostType>::bellman_ford( int source, vector<int> &distance )//序号为source的节点作为源点.
{
	initialize_single_source( source, distance );
	for( int i = 1; i < vex_num; ++i )//最远的点经过vertex_num-1条边也到了.
	{
		for( vector< Vertex<VertexType, CostType> >::iterator 
			 iter = vertex_table.begin();
			 iter != vertex_table.end();
			 ++iter )
			 {
				int begin = iter->serial_number;
				Edge<VertexType, CostType> *edge = iter->first_adj;
				while( edge != NULL )
				{
					int end = edge->end_vertex;
					relax( begin, end, distance );
					edge = edge->next_adj;
				}
			 }
	}
	for( vector< Vertex<VertexType, CostType> >::iterator 
		 iter = vertex_table.begin();
		 iter != vertex_table.end();
		 ++iter )
		 {
			int begin = iter->serial_number;
			Edge<VertexType, CostType> *edge = iter->first_adj;
			while( edge != NULL )
			{
				int end = edge->end_vertex;
				if( distance[end-1] > distance[begin-1]	+ edge->cost )
					return false;
				edge = edge->next_adj;
			}
		 }
	return true;
}

这个实现的时间复杂度为O(v(v+e))。

Dijkstra算法:

算法将图中的顶点分为两类,一类是已经找到最短路径,记为S,初始状态为空集,另一类就是没找到的,记为Q,初始状态包含图中的所有顶点。Q中的每个顶点维护一个distance变量,表示该顶点到源点s的最短路径估计。每次循环选择Q中具有最小最短路径估计的一个顶点m,将它踢出Q,加入S。然后,松弛m的每条邻接边。直至Q为空。

该算法和广度优先搜索、prim算法有相似之处。因为广搜可以看成是对无权图的单源最短路径算法。对比一下,广搜是每搜完一个顶点后通过它的所有邻接边搜它的所有邻接点,dijkstra是每选出一个顶点后松弛它的所有邻接边。另外,广搜中已经遍历过的顶点相当于已经找到了最短路径,类似于dijkstra中的集合S。和prim算法的相似之处在于二者都将图中的顶点分成两个集合QS,每次都是从集合Q中选择具有最小性质的顶点,然后加入到S中,并对它的邻接点进行更新。另外,二者都使用了贪心思想:每次从Q中选出具有最小性质的顶点。

该算法的一个限制是不能有负权边。

代码地址:点击打开链接

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/march_on/article/details/6926086
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞