zoukankan      html  css  js  c++  java
  • 最短路(SPFA)

    SPFA是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。

    主要思想是:

      初始时将起点加入队列。每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若某个相邻的点修改成功,则将其入队。直到队列为空时算法结束。

      这个算法简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法,所以它也是可以处理负边的。

      SPFA在形式上和广度优先搜索(BFS)非常相似,不同的是BFS中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是说一个点修改过其他的点之后,过了一段时间可能会获得更短的路径,于是再次用来修改其他的点,这样反复进行下去。

      时间复杂度是O(kE),E是边数,K是常数,平均值为2。

      算法实现:

      dis[i]记录从起点s到i的最短路径,w[i][j]记录链接i、j边的长度,pre[v]记录前趋。

      team[1……n]为队列,头指针head,尾指针tail。

      布尔数组exist[1……n]记录一个点是否现在存在在队列中。

      初始化:dis[s]=0,dis[v]=∞(v≠s),memset(exist,false,sizeof(exist));

      起点入队:team[1]=s;head=0;tail=1;exist[s]=true;

    do

    {

      1.头指针向下移一位,取出指向的点u。

      2.exist[u]=false;已被取出了队列。

      3.for与u相连的所有点v   //注意不要去枚举所有点,用数组模拟邻接表储存

        if( dis [ v ] > dis[ u ] + w [ u ][ v ])

        {

          dis [ v ] = dis [ u ] + w [ u ][ v ];

          pre [ v ] = u;

          if ( != exist [ v ])

          {

             尾指针下移一位,v入队;

             exist [ v ] = true;

          }

        }  

    }

    while( head < tail );

    循环队列:

    采用循环队列能够降低队列大小,队列长度只需开到2*n+5即可。

    以上就是标程,根据我个人理解,下面是算法的实现过程:

    先给一道题:

    【题意】
    给出一个图,起始点是1,结束点是N,边是双向的。求点1到点N的最短距离。哈哈,这就是标准的最短路径问题。 
    【输入格式】
    第一行为两个整数N(1≤N≤10000)和M(0≤M≤200000)。N表示图中点的数目,M表示图中边的数目。
    下来M行,每行三个整数x,y,c表示点x到点y之间存在一条边长度为c。(x≠y,1≤c≤10000)
    【输出格式】
    输出一行,一个整数,即为点1到点N的最短距离。
    如果点1和点N不联通则输出-1。
    【样例1输入】
    2 1
    1 2 3
    【样例1输出】
    3

    【样例2输入】
    3 3
    1 2 5
    2 3 5
    3 1 2
    【样例2输出】
    2

    【样例3输入】
    6 9
    1 2 7
    1 3 9
    1 5 14
    2 3 10
    2 4 15
    3 4 11
    3 5 2
    4 6 6
    5 6 9
    【样例3输出】
    20

    我们先来举个例子,一个连通图中共有6个点,每两个点之间有连线(有向、无向都行,这里采用的是无向),边权都已给出,求从1号点到6号点的最短路径长度。

    如图:

      首先,我们先假设所有的点到1的距离都为一个很大的数,例如999999999;

      然后对于1这个点,它可以去三个小伙伴的家里(2号、3号、4号),它先到2号家里,发现距离是7,它又依次到3、5号点,分别发现距离是9和14。现在1号点就不打算待在家里了,它比较懒,就去离自己进的2号家里,所以dis[2]更新为7,dis[3]=9,dis[5]=14。

      其次,这些点有个特点,就是它们都十分enthusiastic,它会不停的问自己家的邻居(与该点连接的其他点):1号点到你们家用不用先来我家,距离也许更短哦?例如2号点,它不会问1号点,因为1号点已经不在他家了(exist[1]=false),他就去问3,4号。因为9<10+7,所以3号点谢绝说:不了,他直接来我家就是最短的,是9。2号点又去问4号说:哎?1号去你家先到我家坐会呗?因为初始化1到每个点的距离都是999999999,所以7+15肯定小于999999999。所以4说:好啊!,先到你家啊,这样1号点就不累了!,所以dis[4]更新为15+7=22,。当2号点全都问完之后,它也要去干别的事了,所以就退出,即exist[2]=false。

      如此循环,直到6号点。

      

      大体的思路就是这样,那么队列是怎么模拟的呢?

    初始化,把1放进队列中,接着,按照顺序把1能走到的点依次放入队列。

    1退出,指针向上移,把2能到达的点依次放入队列中,有过的点就不放。

    如此循环知道队列里只剩6号点位置。

    这就是队列模拟的操作了,代码实现可能会看得更清楚吧:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cstdlib>
    const int N=210000;
    using namespace std;
    int head,tail,st,z,x,y,len=0;
    int lis[N];
    int last[N];
    int dis[N];
    bool exist[N];
    struct bian {//构造边 
        int x,y,d,next;
    };
    bian a[1010000];
    void zxy(int x,int y,int d) {//定义zxy函数 
        len++;
        a[len].x=x;
        a[len].y=y;
        a[len].d=d;
        a[len].next=last[x];
        last[x]=len;
    }
    int main() {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1; i<=m; i++) {
            scanf("%d%d%d",&x,&y,&z);
            zxy(x,y,z);//双向边操作 
            zxy(y,x,z);
        }
        st=1;
        memset(exist,false,sizeof(exist));
        lis[st]=true;
        for(int i=1; i<=n; i++)
            dis[i]=99999999;
        dis[1]=0;
        head=1;
        tail=2;
        while(head!=tail) {
            x=lis[head];
            for(int k=last[x]; k; k=a[k].next) {
                y=a[k].y;
                if(dis[y]>dis[x]+a[k].d) {
                    dis[y]=dis[x]+a[k].d;
                    if(exist[y]==false) {
                        exist[y]=true;
                        lis[tail]=y;
                        tail++;
                        if(tail==n+1)//循环队列 
                            tail=1;
                    }
                }
            }
            lis[head]=0;
            head++;
            if(head==n+1)//循坏队列 
                head=1;
            exist[x]=false;
        }
        if(dis[n]==99999999)
            printf("-1");
        else
            printf("%d",dis[n]);
        return 0;
    }
  • 相关阅读:
    对silverlight布局进行控制,使其居中显示,适用于不同的分辨率
    图(邻接表链表和边表)
    LINUX下GCC编译sqrt函数问题
    图(邻接矩阵)
    表达式树
    赫夫曼树
    N的阶乘中末尾有几个0
    走迷宫
    HDU1863畅通工程(最小生成树 Kruskal)
    KMP算法
  • 原文地址:https://www.cnblogs.com/Zhoier-Zxy/p/8339885.html
Copyright © 2011-2022 走看看