最短路 Dijkstra Floyd Bellman-Ford SPFA模板及例題 (一次性搞定最短路類型的問題)

從城市A到城市B,有時候能夠直達也能夠途徑其餘城市到達,怎樣選擇最短的路徑到達就是最短路問題。node

分爲單源最短路(全部點到某一特定點的最短路徑)和多源最短路(任意兩點間的最短路徑)。根據邊的正負也能夠分爲帶負權邊和不帶負權邊的最短路。ios

Dijkstra:用於解決不含負權邊的單源最短路。基本思想:記S爲已經找到到源點的最短路的點的集合,dis【i】表示頂點i到源點的最短距離。每次取不在S中的dis值最小的點u,將點u加入S並優化u周圍點的dis值,重複直至全部點都在S中。算法

Dijkstra僞代碼:數組

(1)初始化S,將源點加入S。閉包

(2)初始化dis數組:for(T中每一個與源點S有邊相連的點u)dis【u】=min(dis【u】,w(u,s));優化

(2)while(S不包含全部的頂點)do{this

                   u=min(dis【u】&& u不在S中);spa

                   u加入S;code

                   for(每一個不在S中的頂點v && dis【v】>dis【u】+w(u,v))dis【v】=dis【u】+w(u,v);排序

          }

由於while循環和每次都要找dis最小的點,算法的時間複雜度爲o(n^2),因此一般採用Dijkstra的隊列優化版來解題(採用優先隊列保存S中的點,這樣就不用每次都去找dis最小的點)

隊列優化模板:

/*zhizhaozhuo
Dijkstra優先隊列模板 AND HUD-2544*/
#include<cstdio> 
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=110,INF=1e9;
int vis[maxn],dis[maxn];
struct node{
	int to,len;
	node(int to,int len):to(to),len(len){} //方便插入vector
	bool operator<(const node& a)const{return len>a.len;}//用於優先隊列排序,從小到大排
};
vector<node>Map[maxn];
void Dijkstra(int s,int n){
	for(int i=1;i<=n;i++)dis[i]=INF;
	memset(vis,0,sizeof(vis));
	dis[1]=0;
	priority_queue<node>Q;
	Q.push(node(1,dis[1]));
	while(!Q.empty()){
		node u=Q.top();Q.pop();
		if(vis[u.to])continue;
		vis[u.to]=1;
		for(int i=0;i<Map[u.to].size();i++){
			node v=Map[u.to][i];
			if(dis[v.to]>dis[u.to]+v.len){
				dis[v.to]=dis[u.to]+v.len;
				Q.push(node(v.to,dis[v.to]));
			}
		}
	}
}
int main(){
	int n,m;
	while(~scanf("%d%d",&n,&m)&&n){
		for(int i=0;i<=n;i++)Map[i].clear();
		for(int i=0;i<m;i++){
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			Map[a].push_back(node(b,c));
			Map[b].push_back(node(a,c));
		}
		Dijkstra(1,n);
		printf("%d\n",dis[n]);
	}
	return 0;
}

UVA:12661 Funny Car Racing 。

每一條路有一個開放時間,關閉時間,經過時間。分兩種狀況判斷

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int N=310,INF=1<<30;
int d[N];
bool inq[N];
struct Edge{
	int u,v,a,b,t;
};
struct node{
	int p,t;
	node(int p=0,int t=0):p(p),t(t){}
	bool operator<(const node&a)const{return t>a.t;}
};
vector<Edge>edges;
vector<int>G[N];
int n,m,S,T;
void AddEdge(int u,int v,int a,int b,int t){
	edges.push_back((Edge){u,v,a,b,t});
	int m=edges.size();
	G[u].push_back(m-1);
}
void Dijkstra(){
	priority_queue<node>Q;
	memset(inq,0,sizeof(inq));
	for(int i=1;i<=N;i++)d[i]=INF;
	d[S]=0;
	inq[S]=true;
	Q.push(node(S,0));
	while(!Q.empty()){
		node now=Q.top();Q.pop();
		inq[now.p]=false;
		for(int i=0;i<G[now.p].size();i++){
			Edge e=edges[G[now.p][i]];
			int l1=d[now.p]%(e.a+e.b),l2; 
			if(l1+e.t<=e.a)l2=e.t;  //能直接經過 
			else l2=e.a+e.b-l1+e.t; //須要等待 
			if(d[now.p]+l2<d[e.v]){
				d[e.v]=d[now.p]+l2;
				if(!inq[e.v])Q.push(node(e.v,d[e.v]));
			}
		}
	}
}
int main(){
	int Case=0;
	while(~scanf("%d%d%d%d",&n,&m,&S,&T)){
		for(int i=1;i<=n;i++)G[i].clear();
		edges.clear();
		for(int i=0;i<m;i++){
			int u,v,a,b,t;
			cin>>u>>v>>a>>b>>t;
			if(a>=t)AddEdge(u,v,a,b,t);
		}
		Dijkstra();
		printf("Case %d: %d\n",++Case,d[T]);
	}
	return 0;
}

 

Floyd算法(多源最短路徑算法):由於dis的值須要初始化爲INF,這時候就存在一個潛在的問題。若是INF過小可能會使INF的邊成爲最短路的一部分,若是INF過大d[i][k]+d[k][j]可能會溢出。

主算法中只須要將 d[i][j]=min(d[i][j],d[i][k]+d[k][j])  改成 d[i][j]=d[i][j] || (d[i][k]&&d[k][j]))結果就是有向圖的傳遞閉包。

void Floyd(int n){
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++)if(i!=j&&i!=k&&j!=k){
                if(d[i][j]<INF && d[k][j]<INF)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}

Floyd算法例題:FZU 2271 (在不改變全部點之間最短距離的狀況下刪除儘量多的邊) 

重邊與自環輸入時就處理掉,重邊保留最小的邊。對題目的圖進行兩次Floyd,一次找到全部點之間的最短距離,第二次則模擬floyd的優化過程,若是當前的距離大於已獲得最短距離或者存在一箇中轉點使得距離相等,那麼這條邊就能夠刪除。

/*zhizhaozhuo FZU 2271*/ 
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=150,inf=1e8;
int map[N][N],b[N][N];
int n,m;
int main(){
	int t,x,y,z,Case=0;
	scanf("%d",&t);
	while(t--){
		int cnt=0;
		memset(map,0,sizeof(map));
		scanf("%d%d",&n,&m);
		for(int i=1; i<=n; i++)
			for(int j=1; j<=n; j++)
				map[i][j]=(i==j)?0:inf;//圖的預處理 
		for(int i=1; i<=m; i++){
			scanf("%d%d%d",&x,&y,&z);
			if(map[x][y]!=inf)cnt++;//處理重邊與自環 
			if(map[x][y]>z)map[x][y]=map[y][x]=z;//保留最短的邊 
		}
		memcpy(b,map,sizeof(b));
		for(int k=1;k<=n;k++)//第一次floyd 
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)if(map[i][k]+map[k][j]<map[i][j])map[i][j]=map[i][k]+map[k][j];
		for(int i=1;i<=n;i++)//第二次floyd 
			for(int j=i+1; j<=n; j++)
				for(int k=1; k<=n; k++){
					if(map[i][j]<b[i][j] && b[i][j]!=inf && (i!=j&&k!=j&&i!=k)) {cnt++;break;} //距離大於最短距離 
					if(map[i][j]==map[i][k]+map[k][j] && b[i][j]!=inf &&(i!=j&&k!=j&&i!=k)) {cnt++;break;}//存在中轉點 
				}
		printf("Case %d: %d\n",++Case,cnt);
	}
	return 0;
}

UVA-10048 任意兩點間可能有多條路徑,每一條路有一個噪音值,使得通過兩點的最大噪音值最小

#include<cstdio> 
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int N=100 +10;
int C;
int Map[N][N];
void floyd(){
	for(int k=1;k<=C;k++){
		for(int i=1;i<=C;i++){
			for(int j=1;j<=C;j++){
				if(Map[i][k]!=-1 && Map[k][j]!=-1){
					int temp=max(Map[i][k],Map[k][j]);
					if(Map[i][j]==-1 || Map[i][j]>temp)Map[i][j]=temp;
				}
			}
		}
	}
}
int main(){
	int T=0,S,Q;
	while(scanf("%d%d%d",&C,&S,&Q)==3 &&C){
		for(int i=1;i<=C;i++)for(int j=1;j<=C;j++)Map[i][j]=-1;
		int c1,c2,d;
		while(S--){
			scanf("%d%d%d",&c1,&c2,&d);
			Map[c1][c2]=d;
			Map[c2][c1]=d;
		}
		floyd();
		if(T++)printf("\n");
		printf("Case #%d\n",T);
		while(Q--){
			scanf("%d%d",&c1,&c2);
			int ans=Map[c1][c2];
			if(ans==-1)printf("no path\n");
			else printf("%d\n",ans);
		}	
	}
	return 0;
}

 

Bellman-Ford算法:解決存在負權邊的單源最短路徑問題,時間複雜度爲o(V*E)

(1)dis【i】爲源點s與頂點i之間的路徑長度,初始時dis【i】=INF,dis【s】=0;

(2)枚舉每一條邊(u,v)若dis【v】> dis【u】+w(u,v)那麼dis【v】= dis【u】+w(u,v)

(3)若是步驟(2)沒有更新dis數組,說明最短路查找完畢,不然重複執行(2),但至多執行n-1次

(4)檢測圖中是否存在負環路,枚舉每一條邊(u,v),若是存在 dis【v】> dis【u】+w(u,v)的邊,則存在負環,反之找到了最短路。

SPFA算法:Bellman-Ford算法的隊列實現,減小了沒必要要的冗餘計算(比賽中通常使用SPFA算法)

在Bellman-Ford算法中,並非每一個頂點都會在鬆弛操做中改變,每次都枚舉邊所進行的冗餘計算是沒必要要的時間花費。SPFA算法加入一個隊列保存信息,初始時隊列中只有源點,dis記錄源點到全部點的最短路徑。而後用隊列裏的點更新dis數組的值,若是某一點的dis值被更新,則將該點加入隊尾,重複執行直至隊列爲空,若是某一點進入隊列的次數超過n次,則存在負環。

下面給出白書的模板(紫書的模板是從起點出發的,若是沒有找到負環可能只是起點到達不了負環,白書在開始時就將點都加入了隊列,就能保證找到負環)

struct Edge{
	int from,to,dist;
	Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
struct BellmanFord{
	int n,m;
	vector<Edge>edges;
	vector<int>G[maxn];
	bool inq[maxn];
	int d[maxn],p[maxn],cnt[maxn];
	
	void init(int n){
		this->n=n;
		for(int i=0;i<n;i++)G[i].clear();
		edges.clear();
	}
	void AddEdge(int from,int to,int dist){
		edges.push_back(Edge(from,to,dist));
		m=edges.size();
		G[from].push_back(m-1);
	}
	bool negativeCycle(int s){
		queue<int>Q;
		memset(inq,0,sizeof(inq));
		memset(cnt,0,sizeof(cnt));
		for(int i=0;i<n;i++){d[i]=0;inq[0]=true;Q.push(i);}
		while(!Q.empty()){
			int u=Q.front();Q.pop();
			inq[u]=false;
			for(int i=0;i<G[u].size();i++){
				Edge& e=edges[G[u][i]];
				if(d[e.to] > d[u]+e.dist){
					d[e.to]=d[u]+e.dist;
					p[e.to]=G[u][i];
					if(!inq[e.to]){
						Q.push(e.to);inq[e.to]=true;
						if(++cnt[e.to]>n)return true;
					}
				}
			}
		}
		return false;
	}
};
BellmanFord solve;

持續更新中。。。。。。

相關文章
相關標籤/搜索