【C++】最短路徑模板題題答

Floyd算法

題目

HihoCoder-1089
vjudge

提示

小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算法

題目

HihoCoder-1089
vjudge

提示

小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算法

題目

HihoCoder-1089
vjudge

提示

“唔……地點很多,道路很少,這個鬼屋是一個稀疏圖,既然這一點被特地標註出來,那麼想來有其作用的咯?”小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&ltq’,那麼(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章