SPFA求單源最短路徑

最短路徑的算法有不少,各有優劣。
好比Dijkstra(及其堆(STL-priority_queue)優化),可是沒法處理負環的狀況;
好比O(n^3)的Floyd算法;好比Bellman-Ford算法,能夠處理負環的狀況。git

SPFA算法就是基於Bellman-Ford算法的改進。
SPFA,全稱爲Shortest Path Faster Algorithm,也被不少Oler笑稱爲Super Fast Algorithm.
無能否認的是,SPFA的效率的確很高。算法


邏輯與思路

SPFA的核心代碼很短,只有三十行(可是還有各類初始化)。
乍一看就是一個廣度優先搜索。下文的代碼是一個指針操做的,進行必定優化的,使用一個不太常見的方法存邊寫的spfa函數。數組

初始化

1.將全部點的距離設爲INF(memset(0x3f)),及無窮大,將到源點的距離設爲(dis[st] = 0);
2.將源點壓入隊列q;(q.push_back(st));網絡

循環執行直到隊列爲空

取出隊首節點cur,對全部與cur相連的節點進行如下操做:
若是源點到cur的距離與cur到該節點距離的和小於源點到該節點的距離,則更新源點對該節點的距離,並將該節點壓入隊列;
也就是: if(dis[cur] + edge[cur, i] < dis[i]) update(dis[i]); q.push_back(i);函數

最終獲得的各點距離就是最短路徑(若是不連通,則距離爲初始值INF)。優化


原理:

從直覺層面上想,這不難理解。
若是該節點的最短路徑被更新(也就是變小),則說明經過該節點到其餘節點的路徑長度便有可能所以變小。
因而壓入隊列,等待下一次操做。spa

優化

用一個雙端隊列維護。
若是獲得新的距離dis[ne]小於位於隊首的距離dis[q.front()],則將該節點壓入隊首,反之則壓入隊尾。實測效率的確更高。指針

另外一個優化是隊列節點進出現一次。
用一個數組wh[I]表示節點i是否在隊列中,若是在則只更新距離不壓入隊列(由於隊列裏有)。code

圖的存儲方式

大體有三種方式存儲。
一是鄰接矩陣,不推薦,除了好寫一點,又費空間又費時間。
二是前向心,存每條弧的next指向與之節點相連的另外一節點。不常寫,不作評價。
三是將全部弧存入一個vector(或者數組),記錄全部節點與之相連弧的編號。下文的代碼實現即是基於這種存儲方式。隊列

具體存儲方式:
1.讀入每一條弧的信息,將它們存入vector中。
2.每次存儲時,將該弧的標號(或者指針)存到另外一個vector中
例如讀入一條弧: edge a -> b, weight(a, b) = c.
存邊的結構體存儲每條弧的始點,終點與權重(最短路徑則不須要存權重)

struct Edge{
    int st, en, weight;
    Edge(){}
    Edge(int s, int e, int w):
        st(s), en(e), weight(w){}
};

那麼按照如下操做。(若是是有向圖則不須要存回邊)

read(a); read(b); read(c);
edge.push_back(Edge(a, b, c));
edge.push_back(Edge(b, a, c));
arc[a].push_back(edge.size()-2);
arc[b].push_back(edge.size()-1);

固然也可選擇只存一次邊,可是若是是存儲網絡流,則必須這麼存。

那麼遍歷全部與節點x相連的弧即是這樣的。

for(int i = 0, i_end_ = arc[x].size(); i < i_end_; ++i)
{
    int j = arc[x][i];
    Edge& e = edge[j];
    ne = e.en;//e.en就是弧的終點
}

SPFA代碼實現

void Spfa()
{
    int q[maxn];
    int *s = q, *t = s + 1, *en = q + n + 1;
    int cur, ne, cur_dis;
    bool wh[maxn] = {0};
    Edge *j;
    memset(dis, 0x3f, sizeof dis);
    dis[st] = 0;
    *s = st;
    while(s != t)
    {
        cur = *s++;
        s = s == en ? q : s;
        wh[cur] = 0;
        REP(i, 0, arc[cur].size())//for(int i = 0; i < arc[cur].size(); ++i)
        {
            j = arc[cur][i];
            ne = j -> en; 
            cur_dis = dis[cur] + j -> weight; 
            if(cur_dis < dis[ne])    
            {
                dis[ne] = cur_dis;
                if(wh[ne])  continue;
                wh[ne] = 1;
                if(dis[ne] < dis[*s])
                {
                    s = s == q ? en - 1 : s - 1;
                    *s = ne;
                }
                else *t++ = ne;
                t = t == en ? q : t;
            }
        }
    }
    return ;
}

以上代碼爲了提升總體效率,犧牲了必定可讀性,基本使用指針操做
可是效率的提升是很是可觀的。從800+ms -> 400+ms。

能夠在洛谷上交一下板子題。
luogu P3371 【模板】單源最短路徑


完整代碼實現

/*
About:
From: luogu 3371
Auther: kongse_qi
Date:2017/05/24
*/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <ctype.h>

namespace IO{
    static int in;
    static char *X, *Buffer;
    static char c;
    void Get_All()
    {
        fseek(stdin, 0, SEEK_END);
        long long file_lenth = ftell(stdin);
        rewind(stdin);
        X = Buffer = (char*)malloc(file_lenth);
        fread(Buffer, 1, file_lenth, stdin);
        return ; 
    }
    
    void Get_Int()
    {
        in = 0;
        while(!isdigit(*X))    ++X;
        while(isdigit(*X))
        {
            in = in * 10 + *(X++) - '0';
        }
        return ;
    }
}//讀入優化,注意必須是文件讀入

using namespace IO;
using namespace std;

#define read(x) Get_Int(), x = in;
#define REP(i, a, b) for (int i = (a), i##_end_ = b; i < i##_end_; ++i)
#define min(a, b) a > b ? b : a

const int maxn = 10005;
const int INF = 2147483647;
const int maxm = 500005;

struct Edge
{
    int st, en, weight;
    Edge(){}
    Edge(int f, int t, int w):
        st(f), en(t), weight(w){}
};

int n, m, st;
int dis[maxn]; 
Edge edge[maxm], *cur = edge;
vector<Edge*> arc[maxn];
int q[maxn]; 

void Add_Edge(int& st, int& en, int& weight)
{
    *cur = Edge(st, en, weight);
    arc[st].push_back(cur++);
    return ;
} 

void Read()
{
    int a, b, c;
    read(n); read(m); read(st);
    REP(i, 0, m)
    {
        read(a); read(b); read(c);
        if(a != b) 
        {
            Add_Edge(a, b, c);
        }
    }
    return ;
}

void Spfa()
{
    int *s = q, *t = s + 1, *en = q + n + 1;
    int cur, ne, cur_dis;
    bool wh[maxn] = {0};
    Edge *j;
    memset(dis, 0x3f, sizeof dis);
    dis[st] = 0;
    *s = st;
    while(s != t)
    {
        cur = *s++;
        s = s == en ? q : s;
        wh[cur] = 0;
        REP(i, 0, arc[cur].size())
        {
            j = arc[cur][i];
            ne = j -> en; 
            cur_dis = dis[cur] + j -> weight; 
            if(cur_dis < dis[ne])    
            {
                dis[ne] = cur_dis;
                if(wh[ne])  continue;
                wh[ne] = 1;
                if(dis[ne] < dis[*s])
                {
                    s = s == q ? en - 1 : s - 1;
                    *s = ne;
                }
                else *t++ = ne;
                t = t == en ? q : t;
            }
        }
    }
    return ;
}

void Print()
{
    int *p = dis + 1;
    REP(i, 1, n + 1)
    {
        *p = *p == 0x3f3f3f3f ? INF : *p; 
        printf("%d ", *p++);
    }
    return ;
}

int main()
{
    freopen("test.in", "r", stdin);
    Get_All(); 
    Read();
    Spfa();
    Print();
    return 0;
}

至此結束。
箜瑟_qi 10:07 2017.05.25

相關文章
相關標籤/搜索