SPFA最短路算法简述

123 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天

SPFASPFA算法

SPFA(Shortest Path Faster Algorithm)算法是单源最短路径的一种算法,通常被认为是 Bellman-ford 算法的队列优化,在代码形式上接近于宽度优先搜索 BFS,是一个在实践中非常高效的单源最短路算法。

①、SPFASPFA算法流程

SPFASPFA 算法中,使用 did_i表示从源点到顶点ii的最短路,额外用一个队列来保存即将进行拓展的顶点列表,并用 inqiinq_i来标识顶点ii是不是在队列中。

1.初始队列中仅包含源点,且源点 ssds=0d_s=0

2.取出队列头顶点 uu,扫描从顶点 uu 出发的每条边,设每条边的另一端为 vv,边<u,v><u,v> 权值为 ww,若 du+w<dvd_u+w<d_v,则

  • dvd_v修改为 du+wd_u+w

  • vv 不在队列中,则

  • vv 入队

3.重复步骤 2 直到队列为空

最终d d 数组就是从源点出发到每个顶点的最短路距离。如果一个顶点从没有入队,则说明没有从源点到该顶点的路径。

SPFASPFA 的空间复杂度为O(V) \mathcal{O}(V)。如果顶点的平均入队次数为 kk,则 SPFASPFA 的时间复杂度为 O(kE)\mathcal{O}(kE)O,对于较为随机的稀疏图,根据经验 kk 一般不超过 4。

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;

struct edge{
    int v, w, fail;
    edge(){}
    edge(int _v, int _w, int _fail){
        v = _v;
        w = _w;
        fail = _fail;
    }
}e[M << 1];
int head[N], len;
void init(){
    memset(head, -1, sizeof(head));
    len = 0;
    
}

void add(int u, int v, int w){
    e[len] = edge(v, w, head[u]);
    head[u] = len++;
}

void add2(int u, int v, int w){
    add(u, v, w);
    add(v, u, w);
}

int n, m;
int dis[N];
bool vis[N];

void spfa(int u){
    memset(vis, false, sizeof(vis));
    vis[u] = true;
    memset(dis, 0x3f, sizeof(dis));
    dis[u] = 0;
    queue<int> q;
    q.push(u);
    while(!q.empty()){
        u = q.front();
        q.pop();
        vis[u] = false;
        for(int j = head[u];~j;j = e[j].fail){
            int v = e[j].v;
            int w = e[j].w;
            if(dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                if(!vis[v]){
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
}

int main() {
    
    init();
    int u, v, w;
    cin>>n>>m;
    while(m--){
        cin>>u>>v>>w;
        add2(u, v, w);
    }
    spfa(1);
    cout<<dis[n]<<endl;
    
    return 0;
}

②、SPFASPFA判断负环

DijkstraDijkstra不能处理有负权的图,而 SPFASPFA 可以处理任意不含负环(负环是指总边权和为负数的环)的图的最短路,并能判断图中是否存在负环

但是 SPFASPFA 可以用来判断负环,在进行 SPFASPFA 时,用一个数组 cnticnt_i来标记每个顶点入队次数。如果一个顶点入队次数 cnticnt_i大于顶点总数 n,则表示该图中包含负环。一般情况下,SPFASPFA 判负环都只用在有向图上,因为在无向图上,一条负边权的边就是一个负环了

memset(in, 0, sizeof in);
in[u] = 1;
// 修改入队部分的操作
if(!vis[v]){
    q.push(v);
    vis[v] = true;
    ++in[v];
    if(in[v] > n){
        return true;
    }
}