Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV)
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)
SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).
Floyd:每对节点之间的最短路径。
Bellman-Ford算法
Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 G=(V,E),其源点为s,加权函数 w 是边集 E 的映射。对图G运行Bellman-Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点s可达的负权回路。若不存在这样的回路,算法将给出从源点s到图G的任意顶点v的最短路径d[v]。
Bellman-Ford算法流程分为三个阶段:
(1)初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;
(2)迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)
(3)检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。
适用条件和范围:
1.单源最短路径(从源点s到其它所有顶点v);
2.有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);
3.边权可正可负(如有负权回路输出错误提示);
4.差分约束系统;
bool bellman()
{
bool flag ;
for(int i=0;i<n-1;i++)
{
flag=false;
for(int j=0;j<all;j++) //穷举每条边
if(dis[t[j].to]>dis[t[j].from]+t[j].vis) //松弛判断
{
dis[t[j].to]=dis[t[j].from]+t[j].vis; //松弛操作
flag=true;
}
if(!flag)
break;
}
for(int k=0;k<all;k++) //对所有边进行一次遍历,判断是否有负回路
if(dis[t[k].to]>dis[t[k].from]+t[k].vis)
return true;
return false;
}
Dijkstra算法
算法流程:
(a) 初始化:用起点v到该顶点w的直接边(弧)初始化最短路径,否则设为∞;
(b) 从未求得最短路径的终点中选择路径长度最小的终点u:即求得v到u的最短路径;
© 修改最短路径:计算u的邻接点的最短路径,若(v,…,u)+(u,w)<(v,…,w),则以(v,…,u,w)代替。
(d) 重复(b)-©,直到求得v到其余所有顶点的最短路径。
特点:总是按照从小到大的顺序求得最短路径。
假设一共有N个节点,出发结点为s,需要一个一维数组vis[N]来记录前一个节点序号,一个一维数组dis[N]来记录从原点到当前节点最短路径(初始值为s到Vi的边的权值,没有则为+∞),一个二维数组map[N][N]来记录各点之间边的权重,按以上流程更新map[N]和dis[N]。
void dijs(int v)//v为原点
{
int i,j,k;
for(i=1;i<=n;i++)
dis[i]=map[v][i];//初始化
memset(vis,0,sizeof(vis));
vis[v]=1;
for(i=2;i<=n;i++)
{
int min=INF;
k=v;
for(j=1;j<=n;j++)
{
if(!vis[j]&&min>dis[j])
{
k=j;
min=dis[j];//在dis中找出最小值
}
}
vis[k]=1;//使k为已生成终点
for(j=1;j<=n;j++)//修改dis
{
if(dis[j]>dis[k]+map[k][j])
dis[j]=dis[k]+map[k][j];
}
}
}
SPFA算法
求最短路径的算法有许多种,除了排序外,恐怕是OI界中解决同一类问题算法最多的了。最熟悉的无疑是Dijkstra,接着是Bellman-Ford,它们都可以求出由一个源点向其他各点的最短路径;如果我们想要求出每一对顶点之间的最短路径的话,还可以用Floyd-Warshall。
SPFA是这篇日志要写的一种算法,它的性能非常好,代码实现也并不复杂。特别是当图的规模大,用邻接矩阵存不下的时候,用SPFA则可以很方便地面对临接表。每个人都写过广搜,SPFA的实现和广搜非常相似。
如何求得最短路径的长度值?
首先说明,SPFA是一种单源最短路径算法,所以以下所说的“某点的最短路径长度”,指的是“某点到源点的最短路径长度”。
我们记源点为S,由源点到达点i的“当前最短路径”为D[i],开始时将所有D[i]初始化为无穷大,D[S]则初始化为0。算法所要做的,就是在运行过程中,不断尝试减小D[]数组的元素,最终将其中每一个元素减小到实际的最短路径。
过程中,我们要维护一个队列,开始时将源点置于队首,然后反复进行这样的操作,直到队列为空:
(1)从队首取出一个结点u,扫描所有由u结点可以一步到达的结点,具体的扫描过程,随存储方式的不同而不同;
(2)一旦发现有这样一个结点,记为v,满足D[v] > D[u] + w(u, v),则将D[v]的值减小,减小到和D[u] + w(u, v)相等。其中,w(u, v)为图中的边u-v的长度,由于u-v必相邻,所以这个长度一定已知(不然我们得到的也不叫一个完整的图);这种操作叫做松弛。
引用内容 引用内容
松弛操作的原理是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。所谓对i,j进行松弛,就是判定是否d[j]>d[i]+w[i,j],如果该式成立则将d[j]减小到d[i]+w[i,j],否则不动。
(3)上一步中,我们认为我们“改进了”结点v的最短路径,结点v的当前路径长度D[v]相比于以前减小了一些,于是,与v相连的一些结点的路径长度可能会相应地减小。注意,是可能,而不是一定。但即使如此,我们仍然要将v加入到队列中等待处理,以保证这些结点的路径值在算法结束时被降至最优。当然,如果连接至v的边较多,算法运行中,结点v的路径长度可能会多次被改进,如果我们因此而将v加入队列多次,后续的工作无疑是冗余的。这样,就需要我们维护一个bool数组Inqueue[],来记录每一个结点是否已经在队列中。我们仅将尚未加入队列的点加入队列。
void spfa()
{
int i,k;
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
dis[i]=INF;//初始化
dis[1]=0;
queue<int>q;//创建队列
vis[1]=1;
q.push(1);//源点放入队尾
while(!q.empty())
{
k=q.front();//从队首取出一个节点,扫描所有从该节点可以到达的终点
q.pop();
vis[k]=0;
for(i=1;i<=n;i++)
{
if(dis[i]>dis[k]+map[k][i])//松弛判断
{
dis[i]=dis[k]+map[k][i];//松弛操作
if(vis[i]==0)//判断这个点是否在队列里面,如果不在加入队列
{
q.push(i);
vis[i]=1;
}
}
}
}
}
Floyd算法
这里需要用到动态规划的思想,对于任何一个城市而言,i 到 j 的最短距离不外乎存在经过 i 与 j 之间的k和不经过k两种可能,所以可以令k=1,2,3,…,n(n是城市的数目),再检查d(ij)与d(ik)+d(kj)的值;在此d(ik)与d(kj)分别是目前为止所知道的 i 到 k 与 k 到 j 的最短距离,因此d(ik)+d(kj)就是 i 到 j 经过k的最短距离。所以,若有d(ij)>d(ik)+d(kj),就表示从 i 出发经过 k 再到j的距离要比原来的 i 到 j 距离短,自然把i到j的d(ij)重写为d(ik)+d(kj)<这里就是动态规划中的决策>,每当一个k查完了,d(ij)就是目前的 i 到 j 的最短距离。重复这一过程,最后当查完所有的k时,d(ij)里面存放的就是 i 到 j 之间的最短距离了<这就是动态规划中的记忆化搜索>。利用一个三重循环产生一个存储每个结点最短距离的矩阵.
用三个for循环把问题解决了,但是有一个问题需要注意,那就是for循环的嵌套的顺序:我们可能随手就会写出这样的枚举程序,但是仔细考虑的话,会发现是有问题的:
for i:=1 to n do
for j:=1 to n do
for k:=1 to n do
问题出在我们太早的把i-k-j的距离确定下来了,假设一旦找到了i-p-j最短的距离后,i到j就相当处理完了,以后不会在改变了,一旦以后有使i到j的更短的距离时也不能再去更新了,所以结果一定是不对的。所以应当象下面一样来写程序:
for k:=1 to n do
for i:=1 to n do
for j:=1 to n do
if …
这样作的意义在于固定了k,把所有i到j而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的i到j都处理完后,才会移动到下一个K。
for(i=0;i<=m;i++)
for(j=0;j<=n;j++)
map[i][j]=INF;//初始化
for(k=1;k<=n;k++)//动态规划的思想
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(i==j)
continue;
if(map[i][j]>map[i][k]+map[k][j])
map[i][j]=map[i][k]+map[k][j];
}
作者:Calm微笑
来源:CSDN
原文:https://blog.csdn.net/yao1373446012/article/details/50773450