1. 程式人生 > 實用技巧 >模板 --單源最短路

模板 --單源最短路

模板 --單源最短路

求最短路一般有兩種方法,dij,SPFA;

大多數情況下最常用並且最穩妥的就是dij,SPFA一般用於判斷負權值和負環,並且如果邊較多,SPFA容易被卡死。所以一般情況下都是使用dij。

首先先介紹dij

dij演算法的主要思想是先尋找一個點A1,將這個點併入一個集合,然後找出與這個點相連的點中路徑最小的點A2,然後將A2併入集合,將與A2相連點A3的路徑加上A1到A2點的路徑與A1到A3的路徑比較,如果前者小於後者,就將A1到A3路徑更新。

以此類推,直到將所有點加入集合中。我們用一個數組儲存點A到所有點的最短路徑。

圖的儲存也有多種方式,例如二維陣列儲存(鄰接矩陣),C++中STL裡的vector儲存,還有鏈式前向星(鄰接表)。其中用二維陣列儲存只能使用與資料範圍較小的題,如果資料範圍超過1e5,基本都是使用vector和鏈式前向星儲存。

下面先放一個二維陣列儲存的程式碼模板;

void Dij(int s) // s為起點
{
    memset(vis, 0, sizeof(vis));
    dis[s]=0;
    for(int i = 1; i <= n; i++)dis[i] = INF;//INF為一個很大的數
    int v,mn;
    for(int i = 1; i <= n; i++)
    {
        mn = INF;
        for(int j = 1; j <= n; j++)//找最小值的點
        {
            if(!vis[j] && dis[j] < mn)
            {
                mn = dis[j];
                v = j;
            }
        }
        vis[v] = 1;
        for(int j = 1; j <= n; j++)
        {
            if(!vis[j] && (mn + mp[v][j]) < dis[j])dis[j] = mn + mp[v][j];
        }
    }
}

一般情況下不推薦使用鄰接矩陣存圖,很容易TLE;

下面是使用C++STL中的vector存圖;

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
#define long long ll;
using namespace std;
const int MAXN= 0x3f3f3f3f;
const int INF =2147483647;
struct node{
    int v;
    int w;
};//建立一個結構體,到達點和距離。
node make_out(int x,int y)
{
    node t;
    t.v = x;
    t.w = y;
    return t;
//將到達點和距離存入vector。
}
vector<node> q[500001];
int n, m, s;
int d[500001];
int vis[500001];
void dij(int x)
{
        memset(vis, 0, sizeof(vis));
         for (int i = 1; i <= n;i++)
       { d[i] = INF;}//初始化賦值為最大值
    for (int i=0; i < q[x].size();i++)
    {
        int h = q[x][i].v;
        d[h] = q[x][i].w;
    }//更新與X到與X相鄰點的距離
    d[x] = 0;//到本身的距離為0
    int pos;
    for (int i = 1; i < n; i++)//尋找路程最小的點
    {
        int mm = INF;
        for (int j = 1; j <= n; j++)
        {
            if (!vis[j] && mm > d[j])
            {
                mm = d[j];
                pos = j;
            }
        }
        vis[pos] = 1;
        for (int k = 0; k < q[pos].size(); k++)//鬆弛操作
        {
            int h = q[pos][k].v;
            if (d[h]>q[pos][k].w +d[pos])
            {
                d[h] = q[pos][k].w +d[pos];
            }
        }
    }
}
int main()
{
    cin >> n >> m >> s;
        for (int i = 1; i <= m; i++)
        {
            int x, y, z;
            cin >> x >> y >> z;
            q[x].push_back(make_out(y, z));
        }
        dij(s);
        for (int i = 1; i <= n;i++)
        {
    
            cout << d[i]<<' ';
        }
        return 0;
}





然後就是使用鏈式前向星,跟Vector一樣都是鄰接表儲存方式。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
using namespace std;
const int INF= 0x3f3f3f3f;
const int MAXN = pow(2, 31) - 1;
int head[500001];//head是儲存最新起始點的位置
int ans = 0;
queue<int> q;
struct edge
{
    int u, v, w, next;//next是儲存上一條邊的位置
}e[500001];
int n, m;
void add(int u,int v,int w)//鏈式前向星存圖
{
    e[++ans].next = head[u];
    e[ans].v = v;
    e[ans].w = w;
    head[u] = ans;
}
int d[500001];
int vis[500001];
void DIJ(int x)
{
    memset(vis, 0, sizeof(vis));//跟vector類似
    for (int i = 1; i <= n;i++)
    {
        d[i] = MAXN;
    }
        d[x] = 0;

        int pos = x;
        while (!vis[pos])//這裡的意思是是否訪問所有點的
            {for (int i = head[pos]; i != 0;i=e[i].next)
            {
                int h = e[i].v;//鬆弛操作
                if(d[h]>d[pos]+e[i].w)
                {
                    d[h] = d[pos] + e[i].w;
                }
            }
            vis[pos] = 1;
            int mn = MAXN;
            for (int j = 1; j <= n; j++)//尋找最大值的點
            {
                if (!vis[j] && mn > d[j])
                {
                    mn = d[j];
                    pos = j;
                }

                }
                
            }
}
int main()
{
    int s;
    cin >> n >> m>>s;
    for (int i = 1; i <= m;i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }
   DIJ(s);
    for (int i = 1; i <= n;i++)
    {
        cout << d[i] << " ";
    }
        return 0;
}



以上三種方式都是使用普通的dij,但是有時候會遇到一些資料很大的,dij還是可以優化的,最常用的就是dij的堆優化。

所謂的堆優化就是使用C++STL中的優先佇列來維護dij,dij中有一步是尋找距離最小的點。使用堆優化就可以減少時間複雜度。

下面放堆優化模板

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int MAXN= 0x3f3f3f3f;
struct node{
    int u;
    int s2;
    bool operator <(const node& r)const//過載優先佇列,最小堆
    {
        return r.s2 < s2;
    }
};
struct MS
{
    int u, v, w, next;
}e[200001];
int head[200001];
int ans = 0;
void add(int u,int v,int w)//鏈式前向星
{
    e[++ans].next = head[u];
    e[ans].v = v;
    e[ans].w = w;
    head[u] = ans;
}
priority_queue<node> q;//優先佇列
int d[200001];
int n, m,s;
void dij()
{
    for (int i = 1; i <= n;i++)
       { d[i] = 2147483647;}
        d[s] = 0;
    q.push((node){s, 0});//將距離和點放入優先佇列中
while(!q.empty())
{
    node t = q.top();//取堆頂元素,距離一定是最小的
    q.pop();
    int u = t.u;
    int s1 = t.s2;
     if(s1!=d[u])//這步是堆優化的基本操作,防止沒更新的進入迴圈,減少時間複雜度
            continue;
    for (int i = head[u]; i != 0;i=e[i].next)
    {int v1 = e[i].v;
        if (d[v1]>d[u]+e[i].w)//鏈式前向星基本遍歷操作,鬆弛操作
        {
            d[v1] = d[u] + e[i].w;
            q.push((node){v1, d[v1]});//入隊
        }
    }
}
}
int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m;i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }
    dij();
    for (int i = 1; i <= n;i++)
       { cout << d[i]<<' ';}
        return 0;
}





dij演算法差不多結束了,接下來就是SPFA.

SPFA也是一種求單源最短路的演算法。

其實它的思路跟dij差不多,但是不同的是dij的入集合的點是不會再變動,而SPFA中集合中的點一直有變動,並且SPFA是使用佇列來維護,已經入集合的點也是有可能出來。

下面放程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
using namespace std;
const int INF= 0x3f3f3f3f;
const int MAXN = pow(2, 31) - 1;
struct node{
    int u, v, w, next;
}e[500001];
int head[500001];
int ans = 0;
void add(int u,int v,int w)//鏈式前向星
{
    e[++ans].next = head[u];
    e[ans].v = v;
    e[ans].w = w;
    head[u] = ans;
}
int n, m, s;
queue<int> q;
int vis[500001];
int d[500001];
void SPFA(int x)
{
    for (int i = 1; i <= n;i++)
        d[i] = MAXN;
    d[x] = 0;
    q.push(x);
    vis[x] = 1;//入隊標記為1
    while(!q.empty())
    {
        int h = q.front();
        q.pop();
        vis[h] = 0;//出隊標記為0
        for (int i = head[h]; i!=0;i=e[i].next)
        {
            int t = e[i].v;
           if(d[t]>d[h]+e[i].w)//鬆弛操作
               { d[t] = d[h] + e[i].w;
                if(!vis[t])//如果隊裡不存在相同的點,則入隊。
                {
                    q.push(t);
                }}
        }
    }
}
int main()
{
    cin >> n >> m >> s;
    memset(vis, 0, sizeof(vis));
    memset(d, 0, sizeof(d));
    for (int i = 1; i <= m;i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }
    SPFA(s);
    for (int i = 1; i <= n;i++)
    {
        cout << d[i] << ' ';
    }
        return 0;
}