Floyd算法
題目
提示
小Ho道:“你說的很有道理,我只需要從每個節點開始使用Dijstra算法就可以了!”
小Hi搖搖頭道:“解決問題不是關鍵,學到知識才是關鍵,而且知識本身也遠遠沒有掌握學習的方法重要!”
小Ho只得答道:“好的好的,聽你說便是了!”
於是小Hi便開心道:“這次要說的算法叫做Floyd算法,是一種用於求圖結構上任意兩點間最短距離的算法!”
小Ho嘀咕道:“你都寫標題上了,能不知道麼?”
小Hi強行裝作沒聽到,繼續說道:“這個算法的核心之處在於數學歸納法——MinDistance(i, j)之間最短路徑中可以用到的節點是一點點增加的!”
“你這話每一個字我都聽得懂,但是這句話爲什麼我聽不懂呢……”小Ho無奈道。
“那我這麼說吧,首先,最開始的時候,MinDistance(i, j)——即從第i個點到第j個點的最短路徑的長度,擁有一個限制:這條路徑不能經過任何節點。”小Hi道。
“那就是說如果從i個點到第j個點之間沒有直接相連的邊的話,這個長度就是無窮大咯?”小Ho總結道:“只需要把輸入的邊填進MinDistance中即可!”
“對!”小Hi滿意於小Ho的上道,繼續說道:“然後我放開限制,我允許MinDistance(i, j)——從第i個點到第j個點的最短路徑的長度,擁有的限制,變爲:這條路徑僅允許經過1號節點。”
“這個也簡單,對於兩個節點i, j,我只需要比較MinDistance(i, j)原來的值和MinDistance(i, 1)+MinDistance(1, j)的值,取較小的一個作爲新的MinDistance(i, j)就可以了——畢竟原來的MinDistance都是不經過任何節點,那麼這樣求出來的新的MinDistance(i, j)只有可能經過1號節點。”
“那麼接下來就是關鍵的了,我將限制繼續放寬——路徑僅允許經過1、2號節點。”小Hi繼續說道。
“那其實也沒有任何變化吧,對於兩個節點i, j,我只需要比較MinDistance(i, j)原來的值和MinDistance(i, 2)+MinDistance(2, j)的值,取較小的一個作爲新的MinDistance(i, j),之所以可以這樣是因爲,原來的MinDistance都是在限制“僅允許經過1號節點”下,求出來的,所以新求出來的MinDistance(i, j)也只有可能經過1、2號節點!“
“那我繼續放開限制呢?”小Hi問道。
“也沒有什麼區別了,每放開一個新的節點k允許作爲路徑中的節點,就對於任意的i, j,用MinDistance(i, k)+MinDistance(k, j)去更新MinDistance(i, j),直到1…N號節點都被添加進限制,此時也就等於沒有限制了,那麼這個時候的MinDistance(i, j)就是我們所想要求的值,寫成僞代碼就是這樣!”
for k = 1 .. N
for i = 1 .. N
for j = 1 .. N
若i, j, k各不相同
MinDistance[i, j] = min{MinDistance[i, j], MinDistance[i, k] + MinDistance[k, j]}
“看來你已經很明白了呢!”小Hi嘿嘿一笑,往鬼屋深處跑了去——那麼接下來就是小Ho利用求出的最短路徑,去找到小Hi的時候了!
代碼
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1005;
const int inf=0x3f3f3f;
int n,m;
int f[N][N];
int main() {
scanf("%d%d",&n,&m);
memset(f,inf,sizeof(f));
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
f[u][v]=MIN(f[u][v],w);
f[v][u]=MIN(f[v][u],w);
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
f[i][j]=MIN(f[i][j],f[i][k]+f[k][j]);
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++)
printf("%d ",(i==j) ? 0 : f[i][j]);
printf("\n");
}
return 0;
}
Dijkstra算法
題目
提示
小Ho想了想說道:“唔……我覺得動態規劃可以做,但是我找不到計算的順序,如果我用f[i]表示從S到達編號爲i的節點的最短距離的話,我並不能夠知道f[1]…f[N]的計算順序。”
“所以這個問題不需要那麼複雜的算法啦,我就稍微講講你就知道了!”小Hi道:“路的長度不可能爲負數對不對?”
“那是自然,畢竟人類還沒有發明時光機器……”小Ho點點頭。
於是小Hi問道:“那麼如果就看與S相鄰的所有節點中與S最近的那一個S’,並且從S到S’的距離爲L,那麼有可能存在另外的道路使得從S到S’的距離小於L麼?”
“不能,因爲S’是與S相鄰的所有節點中與S最近的節點,那麼從S到其他相鄰點的距離一定是不小於L的,也就是說無論接下來怎麼走,回到L點時總距離一定大於L。”小Ho思考了一會,道。
“也就是說你已經知道了從S到S’的最短路徑了是麼?”小Hi繼續問道。
“是的,這條最短路徑的長度是L。”小Ho答道。
小Hi繼續道:“那麼現在,我們不妨將S同S’看做一個新的節點?稱作S1,然後我就計算與S相鄰或者與S’相鄰的所有節點中,與S最近的哪一個節點S’’。注意,在這個過程中,與S相鄰的節點與S的距離在上一步就已經求出來了,那麼我要求的只有與S’相鄰的那些節點與S的距離——這個距離等於S與S’的距離加上S’與這些結點的距離,對於其中重複的節點——同時與S和S’相鄰的節點,取兩條路徑中的較小值。”
小Ho點了點頭:“那麼同之前一樣,與S1(即S與S’節點)相鄰的節點中與S’距離最近的節點如果是S’‘的話,並且這個距離是L2,那麼我們可以知道S到S’'的最短路徑的長度便是L2,因爲不可能存在另外的道路比這個更短了。”
於是小Hi總結道:“接下來的問題不就很簡單了麼,只需要以此類推,每次將與當前集合相鄰(即與當前集合中任意一個元素)的所有節點中離S最近的節點(這些距離可以通過上一次的計算結果推導而出)選出來添加到當前集合中,我就能夠保證在每一個節點被添加到集合中時所計算的離S的距離是它與S之間的最短路徑!”
“原來是這樣!但是我的肚子更餓了呢!”言罷,小Ho的肚子咕咕叫了起來。
代碼
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1005;
const int inf=0x3f3f3f;
int n,m,s,t;
int d[N],vis[N];
int a[N][N];
void dijkstra() {
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++) d[i]=inf;
d[s]=0;
for(int i=1; i<=n; i++) {
int k=-1;
for(int j=1; j<=n; j++)
if(!vis[j] && (k==-1 || d[k]>d[j])) k=j;
vis[k]=1;
for(int j=1; j<=n; j++)
if(!vis[j] && d[k]+a[k][j]<d[j])
d[j]=d[k]+a[k][j];
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(a,inf,sizeof(a));
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[u][v]=MIN(a[u][v],w);
a[v][u]=MIN(a[v][u],w);
}
dijkstra();
printf("%d\n",d[t]==inf ? -1 : d[t]);
return 0;
}
Spfa算法
題目
提示
“唔……地點很多,道路很少,這個鬼屋是一個稀疏圖,既然這一點被特地標註出來,那麼想來有其作用的咯?”小Ho道。
“是的,正好有一種最短路徑算法,它的時間複雜度只和邊的條數有關,所以特別適合用來解決這種邊的數量很少的最短路問題!”小Hi點了點頭道:“它就是SPFA算法,即Shortest Path Faster Algorithm。”
“聽上去很厲害的樣子,但是實際上怎麼做的呢?”小Ho問道。
“你會用寬度優先搜索寫這道題麼?”小Hi反問道。
“這個當然會啊,構造一個隊列,最開始隊列裏只有(S, 0)——表示當前處於點S,從點S到達該點的距離爲0,然後每次從隊首取出一個節點(i, L)——表示當前處於點i,從點S到達該點的距離爲L,接下來遍歷所有從這個節點出發的邊(i, j, l)——表示i和j之間有一條長度爲l的邊,將(j, L+l)加入到隊尾,最後看所有遍歷的(T, X)節點中X的最小值就是答案咯~”小Ho對於搜索已經是熟稔於心,張口便道。
“SPFA算法呢,其實某種意義上就是寬度優先搜索的優化——如果你在嘗試將(p, q)加入到隊尾的時候,發現隊列中已經存在一個(p, q’)了,那麼你就可以比較q和q’:如果q>=q’,那麼(p, q)這個節點實際上是沒有繼續搜索下去的必要的——算是一種最優化剪枝吧。而如果q<q’,那麼(p, q’)也是沒有必要繼續搜索下去的——但是它已經存在於隊列裏了怎麼辦呢?很簡單,將隊列中的(p, q’)改成(p, q)就可以了!”
“那我該怎麼知道隊列中是不是存在一個(p, q’)呢?” <額,維護一個position[1…n]的數組就可以了,如果不在隊列裏就是-1,否則就是所在的位置!”< p>
“所以說這本質上就是寬度優先搜索的剪枝咯?”小Ho問道。
小Hi笑道:“怎麼可能!SPFA算法其實是BELLMAN-FORD算法的一種優化版本,只不過在成型之後可以被理解成爲寬度優先搜索的!這個問題,我們會在之後好好講一講的!”
代碼1
用vector實現。
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1e5+5;
const int inf=1e9;
int n,m,s,t;
int v[N],d[N];
vector<int> a[N],b[N];
queue<int> q;
int spfa() {
for(int i=1; i<=n; i++) d[i]=inf;
q.push(s);
v[s]=1;
d[s]=0;
while(!q.empty()) {
int x=q.front();
q.pop();
v[x]=0;
for(int i=0; i<a[x].size(); i++) {
int tp=a[x][i];
if(d[tp]>d[x]+b[x][i]) {
d[tp]=d[x]+b[x][i];
if(!v[tp]) {
q.push(tp);
v[tp]=1;
}
}
}
}
if(d[t]==inf) d[t]=-1;
return d[t];
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[u].push_back(v);
b[u].push_back(w);
a[v].push_back(u);
b[v].push_back(w);
}
printf("%d\n",spfa());
return 0;
}
代碼2
用鏈式前向星實現。
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1e5+5;
const int M=1e6+5;
const int inf=1e9;
struct Edge {
int to,nt,w;
} e[M<<1];
int n,m,s,t,cnt;
int v[N],d[N],h[N];
queue<int> q;
inline void add(int a,int b,int c) {
e[++cnt]=(Edge){b,h[a],c};
h[a]=cnt;
}
int spfa() {
for(int i=1; i<=n; i++) d[i]=inf;
q.push(s);
v[s]=1;
d[s]=0;
while(!q.empty()) {
int x=q.front();
q.pop();
v[x]=0;
for(int i=h[x]; i; i=e[i].nt) {
int tp=e[i].to;
if(d[tp]>d[x]+e[i].w) {
d[tp]=d[x]+e[i].w;
if(!v[tp]) {
q.push(tp);
v[tp]=1;
}
}
}
}
if(d[t]==inf) d[t]=-1;
return d[t];
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(h,-1,sizeof(h));
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
printf("%d\n",spfa());
return 0;
}