/*
*/
把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【模板】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
到这里就结束啦!希望可以看懂!
蒟蒻的第二篇博客(再放个烟花吧!)
posted @ 2019-08-21 23:31  Kyoko_Yosa  阅读(526)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end