模板SPFA(不完全详解)

Posted daz-os0619

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板SPFA(不完全详解)相关的知识,希望对你有一定的参考价值。

一种最短路求法(个人觉得比DIJKSTRA好用)     

用于有向图。

大概思路:从根节点开始,枚举每一个点,同时更新他们所联通的点的最短路径,如果路径被更新,则把这个点入队,一直重复这个操作直到队伍为空为止。  

代码:

struct edge 
    int next, to, v;
e[];//存图,next代表同一个头结点的下一条边 

void spfa(int S) 
    int p, x, y, l, r;
    for (x = 1; x <= n; ++x)
        dis[x] = inf;
    q[0] = S, dis[S] = 0, v[S] = 1;//初始化 
    for (l = r = 0; l != (r + 1) % N; ) 
        p = q[l], ++l %= N;//每次取出队首 
        for (x = first[p]; x; x = e[x].next)//遍历与队首同头的每一条边 
            if (dis[p] + e[x].v < dis[(y = e[x].to)]) //如果可以更新 
                dis[y] = dis[p] + e[x].v;//更新 
                if (!v[y]) 
                    v[y] = 1;
                    q[++r %= N] = y;//入队 
                
            
        v[p] = 0;
    

但是这种做法容易被卡掉(神奇奶牛),所以可以采用SLF优化。

SLF优化:每一次都把即将入队的值与队首值比较,若比队首值小,则存入队首。

为什么呢?

因为每一次都会从队首开始遍历,当队首是最小值时,被更新的所以节点的值也会是最小值,这样可以节省很大一部分时间。

(有点抽象。。举个例子吧)

(我不想画图)

技术图片技术图片技术图片

 

这里需要注意:只有dis[]存储路径值,q[]存储的是当前最小路径值所在的位置,包括edge里的next也是同一个起点的上一对点的序号。(这里的头就是First[]的下标)

这里各种各样的序号很多。。特别容易弄混。会把各种序号分段输出的程序放在结尾,看不懂的话试几组样例看看输出会很有帮助。

到这里只是第一次更新。

下面是第二次更新:

 技术图片

接下来就可以以此类推了。。如果看不懂的话下面是分段输出的代码,结合上面的图看,体会一下中心思想。

代码

#include<iostream>
using namespace std;
struct edge 
    int next, to, v;
    edge()
    edge(int x,int y,int z)
    
        next=x;
        to=y;
        v=z;
    
e[10001];

int first[10001];
int tot;
int dis[100001];
int q[100001];
int v[100001];
void add_edge(int x, int y,int z) 
    e[++tot] = edge(first[x], y,z);
    first[x] = tot;

int n;
int N=31;

int inc(int x) 
    x = x + 1;
    x = x % N;
    return x;


int dec(int x) 
    x = x - 1 + N;
    x = x % N;
    return x;


void spfa(int S) 
    int p, x, y, l, r;
    for (x = 1; x <= n; ++x)
        dis[x] = 0x7ffff;
    q[0] = S, dis[S] = 0, v[S] = 1;
    for (l = r = 0; l != (r + 1) % N; ) 
        p = q[l];
        cout<<"从第"<<p<<"号边开始遍历"<<endl; 
        l=inc(l);
        for (x = first[p]; x; x = e[x].next)
        
            cout<<"这时是第"<<x<<"号边"<<endl; 
            if (dis[p] + e[x].v < dis[(y = e[x].to)]) 
                cout<<"更新"<<" "<<""<<dis[y]; 
                dis[y] = dis[p] + e[x].v; 
                cout<<"更新为"<<dis[y]<<endl; 
                if (!v[y]) 
                    v[y] = 1;
                    if (dis[y] < dis[q[l]]) 
                    q[(l=dec(l))] = y;
                    cout<<"从队首插入"<<y<<endl; 
                    cout<<"这时l为"<<l<<"r不变"<<endl; 
                     
                    else 
                    q[(r=inc(r))] = y;
                    cout<<"从队尾插入"<<y<<endl; 
                    cout<<"这时l不变r变为"<<r<<endl; 
                    cout<<"更新后的队列为"<<endl; 
                    for(int i=0;i<=n;i++)
                    
                    cout<<q[i]<<" "; 
                     cout<<endl;
                    cout<<"入队情况为"<<endl;
                    for(int i=1;i<=n;i++)
                    cout<<""<<i<<"号元素"<<v[i]<<" ";
                    cout<<endl;
                
            
        
        v[p] = 0;
        cout<<"此时最短路径被更新为"<<endl; 
        for(int i=0;i<=n;i++)
        
            cout<<dis[i]<<" ";
        
        cout<<endl;
    



int main()

    int m,S;
    cin>>n>>m>>S;
    int x,y,z;
    for(int i=1;i<=m;i++)
    
        cin>>x>>y>>z;
        add_edge(x,y,z);
    
    
    spfa(S);
    cout<<"最终最短路径"<<endl;
    for(int i=1;i<=n;i++)
    
        cout<<dis[i]<<" ";
      
    
    cout<<"一共有"<<tot<<"个节点,分别是"<<endl;
    for(int i=1;i<=tot;i++)
    
        cout<<"与它同起点的上一对点为"<<e[i].next<<""<<" "<<"它指向"<<e[i].to<<"这个点"<<endl;
    
    cout<<"下面输出First数组"<<endl;
        for(int i=1;i<=tot;i++)
    
        cout<<first[i]<<" ";
    

这是举例用的样例:

4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
到这里就结束啦!希望可以看懂!
蒟蒻的第二篇博客(再放个烟花吧!)

以上是关于模板SPFA(不完全详解)的主要内容,如果未能解决你的问题,请参考以下文章

spfa算法详解

图的最短路径-----------SPFA算法详解(TjuOj2831_Wormholes)

最短路径相关常用算法详解

最短路径相关常用算法详解

springboot:thymeleaf使用详解

题目1008:最短路径问题(SPFA算法)