最短路径算法(Shortest Paths Algorithm)

本文详细介绍了四种最短路径算法:松弛技术、Dijkstra算法、Bellman-Ford算法和SPFA算法。讨论了它们的工作原理、适用场景以及如何处理负权边问题。Dijkstra算法适用于正权边,而SPFA在考虑负权边时表现较好。Floyd算法则是求解所有顶点对之间最短路径的动态规划算法。每种算法都有其特点和适用范围,理解这些算法有助于解决图论中的最短路径问题。

          假如你有一张地图,地图上给出了每一对相邻城市的距离,从一个地点到另外一个地点,如何找到一条最短的路? 最短路算法要解决的就是这类问题。定义:给定一个有(无)向图,每一条边有一个权值 w,给定一个起始点 S 和终止点 T ,求从 S 出发走到 T 的权值最小路径,即为最短路径。最短路算法依赖一种性质:一条两顶点间的最短路径包含路径上其他最短路径。简单的说就是:最短路径的子路径是最短路径。这个用反证法很好证明。

一、松弛技术(Relaxation)

了解最短路算法前,必须先了解松弛技术, 为什么叫松弛,有特定原因,有兴趣可以去查查相关资料,如果简化理解松弛技术,它本质上就是一个贪心操作。松弛操作:对每个顶点v∈V,都设置一个属性d[v],用来描述从源点 s 到 v  的最短路径上权值的上界,成为最短路径估计(Shortest-path Estimate),同时π[v]代表前驱。初始化伪代码:

       初始化之后,对所有 v∈V,π[v] = NIL,对v∈V – {s},有 d[s] = 0 以及 d[v] = ∞。松弛一条边(u, v),如果这条边可以对最短路径改进,则更新 d[v] 和 π[v] 。一次松弛操作可以减小最短路径估计的值 d[v] ,并更新 v 的前趋域 π[v]。下面的伪代码对边(u,v)进行了一步松弛操作:

  上边的图示中,左边例子,最短路径估计值减小,右边例子,最短路径估计值不变。当发现 v 到 u 有更近的路径时,更新 d[v] 和 π[v] 。

二、Dijkstra算法

        解决最短路问题,最经典的算法是 Dijkstra算法,它是一种单源最短路算法,其核心思想是贪心算法(Greedy Algorithm),Dijkstra算法由荷兰计算机科学家Dijkstra发现,这个算法至今差不多已有50年历史,但是因为它的稳定性和通俗性,到现在依然强健。另外,Dijkstra算法要求所有边的权值非负。

Dijkstra算法思想为:设 G = (V, E) 是一个带权有向图,把图中顶点集合 V 分成两组,第一组为已求出最短路径的顶点集合(用S 表示,初始时 S 中只有一个源点,以后每求得一条最短路径 , 就将其加入到集合 S 中,直到全部顶点都加入到 S 中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U 表示),按最短路径长度的递增次序依次把第二组的顶点加入 S 中。在加入的过程中,总保持从源点 v 到 S 中各顶点的最短路径长度不大于从源点 v 到 U 中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S 中的顶点的距离就是从 v 到此顶点的最短路径长度,U 中的顶点的距离,是从 v 到此顶点只包括 S 中的顶点为中间顶点的当前最短路径长度。伪代码:

        第 1 行将 d 和 π 初始化,第 2 行初始化集合 S 为空集,4 ~ 8 行每次迭代,都从 U 中选取一个点加入到 S 中,然后所有的边进行松弛操作,即每次迭代,整个图的 d 和 π 都更新一遍。过程本身很简单,下边是图示:

         源点 s 是最左端顶点。最短路径估计被标记在顶点内,阴影覆盖的边指出了前趋的值。黑色顶点在集合 S中,而白色顶点在最小优先队列 Q = V – S 中。a) 第 4 ~ 8 行 while 循环第一次迭代前的情形。阴影覆盖的顶点具有最小的 d 值,而且在第 5 行被选为顶点 u 。b) ~ f) while 循环在第一次连续迭代后的情形。每个图中阴影覆盖的顶点被选作下一次迭代第 5 行的顶点 u。f) 图中的 d 和 π 值是最终结果。

       Dijkstra算法时间主要消耗在寻找最小权值的边,和松弛所有剩余边,所以 EXTRACT-MIN(Q) 这一步,更好的方法是使用优先队列,优先队列可以用二叉堆,斐波那契堆等来实现,下面的代码,我用库自带的优先队列,经这样改造后,效率还是很可观的。

       理解最短路算法,最基础,最简单,最经典的要数这个题目:HDU 2544 最短路,纯粹的算法练习题,用Dijkstra,我写了三个代码来实现。

1)邻接矩阵 + Dijkstra,最简单的方式,当然也是最好理解的方式:

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<climits>
using namespace std; 
const int NV = 101;
const int inf = INT_MAX >> 1; 
int m, n;int map[NV][NV];
int dis[NV];bool mark[NV]; 
int Spfa (int src, int des) 
{    
	for (int i = 0; i <= n; i++)
	{       
		mark[i] = false;
		dis[i] = inf;  
	}  
	queue<int> Q;
	mark[src] = true; 
	dis[src] = 0;    
	Q.push(src);   
	while (!Q.empty())
	{        
		int first = Q.front();  
		Q.pop();     
		mark[first] = false;    
		for (int i = 1; i <= n; i++)
		{    
			if (dis[first] + map[first][i] < dis[i]) 
			{     
				if (!mark[i])
				{                
					Q.push(i);      
				}               
				dis[i] = dis[first] + map[first][i];     
				mark[i] = true;     
			}      
		}//for  
	}//while   
	return  dis[des];
} 
int main() 
{   
	while (~scanf("%d%d", &n, &
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值