模板 --單源最短路
阿新 • • 發佈:2020-09-18
模板 --單源最短路
求最短路一般有兩種方法,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;
}