最短路大汇总

松弛操作:当dis[i] > dis[j] + g[j][i]时 dis[i] = dis[j] + g[j][i]


dijkstra 

单源最短路 O(n^2)

本质是贪心 不能处理负边

分成两个集合 用vis标记 一个是已经找到最短路的一个是没有找到最短路的

从已经找到最短路里的点出发,进行松弛操作 找到其中最小的加入最短路的集合中 再从这个点接下去更新,做n-1次

所有的距离初始化为inf

#include<stdio.h>  
const int N=100;  
const int INF=100000;              //INF假定为无穷大  
int p[N][N],d[N];                  //p表示各节点间的距离,不存在路径即为无穷大;d表示从出发节点到各节点的最短路径长度  
  
void dijkstra(int sec,int n)       //sec为出发节点,n为图中的节点数  
{  
    int i,j,min,min_num;  
    int vis[N]={0,};               //用于标记是否已作为过中途节点,0表示没有,1表示有  
    for(i=0;i<n;i++)               //初始化  
    {  
        d[i]=p[sec][i];  
    }  
    vis[sec]=1;d[sec]=0;           //出发节点到自己的距离永远为0  
    for(i=1;i<n;i++)  
    {  
        min=INF;  
        for(j=0;j<n;j++)           //每次循环取出d数组中的未被作为过中途节点且数值最小的  
        {  
            if(!vis[j]&&d[j]<min)  
            {  
                min=d[j];          //更新最小值  
                min_num=j;         //更新最小值所对应的节点,即记录下标  
            }  
        }  
        vis[min_num]=1;            //标记该节点,表示其已被作为中途节点  
        for(j=0;j<n;j++)           //循环,经过min_num节点到达是否有更小距离,如有更小距离则更新d数组  
        {  
            if(d[j]>min+p[min_num][j])  
            {  
                d[j]=min+p[min_num][j];  
            }  
        }  
    }  
}  
int main()  
{  
    int i,j,n=5;                   //n表示图中的节点个数  
    for(i=0;i<n;i++)               //程序用二维数组p存储各节点间的距离,这里则进行初始化  
    {  
        for(j=0;j<n;j++)  
        {  
            p[i][j]=(i==j?0:INF);  //初始化:i到j路径为无穷大或者i到i本身为0  
        }  
    }  
    p[0][1]=10;p[0][3]=30;p[0][4]=100;p[1][2]=50;p[2][4]=50;p[3][2]=20;p[3][4]=60;  //p[i][j]表示节点i到节点j的距离  
    dijkstra(0,n);                 //求从节点0出发到各节点的最短距离  
    for(i=0;i<n;i++)               //打印从节点0出发到各节点的最短距离  
    {  
        printf(i==n-1?"%d
":"%d ",d[i]);  
    }  
    return 0;  
}  
记录路径:通过path数组来记录路径,path[i]=j表明节点i取得最小路径时,其最后一段走的是节点j到节点i。

然后用栈回溯

可以使用优先队列优化dijkstra, 让权值小的先出列,自定义比较器。使用pair 将结点和权值一起存入队列


Bellman-Ford

单源最短路 可以处理负边 O(mn)

初始:dist(1)[u]=edge[v0,u],v0是源点 

递推:dist(k)[u]=min{dist(k-1)[u],min{dist(k-1)[j]+edge[j,u]}} j=0,1,…,n-1,j<>u; k=2,3,4,…,n-1 。 

dist(i)[u]为从源点v0出发最多经过不构成负权值回路的i条边到达终点u的最短路径长度

n-1次过后便可以得到最短路,如果n-1次后依旧可以更新,那么说明存在负权回路或者是正权回路。 

Bellman-ford算法有一个小优化:每次松弛先设一个标识flag,初值为FALSE,若有边更新则赋值为TRUE,最终如果还是FALSE则直接成功退出

for(int k=1;k<=n-1;k++)//遍历点的次数  
   {  
       for(int i=1;i<=m;i++)//遍历边的次数  
       {  
           if(dis[v[i]]>dis[u[i]]+w[i])//如果从u到v的距离能够通过w这条边压缩路径 就要进行松弛操作  
           {  
               dis[v[i]]=dis[u[i]]+w[i];  
           }  
       }  
   }  



spfa

bellman的队列优化

算法思想:我们用数组d记录每个结点的最短路径估计值,用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止

期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

void spfa(int s)
{
    for(int i = 1; i <= n; i++){
        dis[i] = inf;
        vis[i] = false;
    }
    dis[s] = 0;
    vis[s] = true;
    queue<int> que;
    que.push(s);
    int i, v;
    while(!que.empty()){
        v = que.front();que.pop();
        vis[v] = false;
        for(i = 1; i <= n; i++){
            if(a[v][i] > 0 && dis[i] > dis[v] + a[v][i]){
                dis[i] = dis[v] + a[v][i];
                if(vis[i] == 0){
                    que.push(i);
                    vis[i] = true;
                }
            }
        }
    }

}

  1. SPFA算法有两个优化算法 SLF 和 LLL:
  2. SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,
  3. 否则插入队尾。
  4. LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入
  5. 到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。
可用来解决查分约束问题:将不等式转化为最短路问题【POJ3159 Candies】


Floyd

任意两点最短路 O(n^3)  可处理负边

对于稀疏的图,采用

茂密的图,可以使用

Floyd

算法。

对于稀疏的图,采用n次Dijkstra比较出色,对于茂密的图,可以使用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(int k=0; k<n; k++)//k一定要在最外层

for(int i=0; i<n; i++) 

for(int j=0; j<n; j++) 
    这样作的意义在于固定了k,把所有i到j而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的i到j都处理完后,才会移动到下一个k,这样就不会有问题了


输出路径:

p(ij)的值如果为p,就表示i到j的最短行经为i->...->p->j,也就是说p是i到j的最短行径中的j之前的最后一个城市

一旦发现d(ij)>d(ik)+d(kj),就把p(kj)存入p(ij)

n

tra,对于

茂密的图,可以使用

Floyd

算法。

小技巧:

稀疏图使用邻接表存储

原文地址:https://www.cnblogs.com/wyboooo/p/9643430.html