hdu 2544(所有最短路演算法總結)
最短路
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 82480 Accepted Submission(s): 35690
Problem Description
在每年的校賽裡,所有進入決賽的同學都會獲得一件很漂亮的t-shirt。但是每當我們的工作人員把上百件的衣服從商店運回到賽場的時候,卻是非常累的!所以現在他們想要尋找最短的從商店到賽場的路線,你可以幫助他們嗎?
Input
輸入包括多組資料。每組資料第一行是兩個整數N、M(N<=100,M<=10000),N表示成都的大街上有幾個路口,標號為1的路口是商店所在地,標號為N的路口是賽場所在地,M則表示在成都有幾條路。N=M=0表示輸入結束。接下來M行,每行包括3個整數A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A與路口B之間有一條路,我們的工作人員需要C分鐘的時間走過這條路。
輸入保證至少存在1條商店到賽場的路線。
Output
對於每組輸入,輸出一行,表示工作人員從商店走到賽場的最短時間
Sample Input
2 11 2 33 31 2 52 3 53 1 20 0
Sample Output
32
跟這道題打了三天架,終於把所有最短路演算法總結到一起,話不多說,來一波程式碼。
Floyd:
/*Floyd是十分暴力的一個演算法,複雜度相當高,但是十分簡單,並且可以解決負權問題, 其主體思想為讓每個點都做一次跳板,去鬆弛所有的,這樣可以求出來任意兩個點之間 的最短路,用鄰接矩陣實現*/ #include <iostream> using namespace std; #define inf 0x3f3f3f3f //概念上的無窮大 int n,m,map[105][105]; //鄰接矩陣存地圖 int main() { int i,j,k; int x,y,z; while(cin>>n>>m && n || m) { for(i=1;i<=n;i++) for(j=1;j<=n;j++) { if(i==j) map[i][j]=0; else map[i][j]=inf; //初始化 } for(i=1;i<=m;i++) { cin>>x>>y>>z; if(map[x][y]>z) //解決重邊的問題 { map[x][y]=map[y][x]=z; //構造無向圖 } } for(k=1;k<=n;k++) //每個點都做一次跳板 for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(map[i][j]>map[i][k]+map[k][j]) //對圖中任意所有的點進行鬆弛 map[i][j]=map[i][k]+map[k][j]; } } cout<<map[1][n]<<endl; //直接輸出map[strat][end]就是最短路 } return 0; }
不難看出這個的效率是十分低的,那讓我們來看另一種演算法
Dijkstra:
/*Dijstra相對於Floyd效率還是高了很多的,它的主題思想為 從源點出發,找距離它最短的一個點,再以這個點為跳板,找離跳板最近的點 當不能繼續進行時,再次回到源點,找第二小的點,重複操作,直到所有的點 都被訪問過為止,注意,每個點只能訪問一次,不然會和Floyd一樣複雜*/ #include <iostream> using namespace std; #define inf 0x3f3f3f3f int map[105][105],dis[105],vis[105]; //map存地圖,dis存源點到當前點的距離,vis存訪問狀態 int n,m; void dijkstra(int start) { int i,j,k; for(i=1; i<=n; i++) { dis[i]=map[start][i]; //對dis進行初始化 } for(i=1;i<=n;i++) vis[i]=0; //0表示沒有被訪問,1表示被訪問 vis[start]=1; for(i=1;i<=n-1;i++) //因為最多訪問n-1個點,所以迴圈n-1次 { int mix=inf; int u; for(j=1;j<=n;j++) { if(vis[j]==0 && mix>dis[j]) { mix=dis[j]; //記錄下距離源點最近的點,並且沒有被訪問 u=j; } } vis[u]=1; //標記為已經被訪問 for(k=1;k<=n;k++) { if(vis[k]==0 && dis[k]>dis[u]+map[u][k]) dis[k]=dis[u]+map[u][k]; //以該點為跳板,對所有點進行鬆弛 } } } int main() { int i,j,k; while(cin>>n>>m && n || m) { for(i=1;i<=n;i++) for(j=1;j<=n;j++) { if(i==j) map[i][j]=0; else map[i][j]=inf; //初始化 } while(m--) { int x,y,z; cin>>x>>y>>z; if(map[x][y]>z) { map[x][y]=map[y][x]=z; //無向圖 } } dijkstra(1); cout<<dis[n]<<endl; } return 0; }
Dijkstra演算法雖然好,但是並不能解決負權問題,更準確的說是判斷有沒有負權的存在,於是就有了另一種演算法,Bellman_Fod:
/*Bellman_Ford的主體思想為對每一條邊進行鬆弛操作,並且是重複進行,最多
重複n-1次,如果n-1後還可以進行鬆弛,說明有負權的存在,但是它並不能解決
負權問題*/
#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int u[2*max+10],v[2*max+10],w[2*max+10],dis[105];
int main() { //u存起點,v存終點,w存權值,dis和迪傑一樣,由於是無向圖,所以要 *2(我在這裡犯了一次錯)
int n,m,i,j,k;
int x,y,z;
while(cin>>n>>m && n || m) {
for(i=1; i<=2*m; i++) {
cin>>x>>y>>z;
u[i]=x;
v[i]=y;
w[i]=z;
j=i+1; //構造無向圖
u[j]=y;
v[j]=x;
w[j]=z;
i++;
}
for(i=1; i<=n; i++)
dis[i]=inf;
dis[1]=0; //將起點設定為零,這樣可以保證從起點開始鬆弛
for(k=1; k<=n-1; k++) { //最多迴圈n-1次
for(i=1; i<=2*m; i++) {
if(dis[v[i]]>dis[u[i]]+w[i])
dis[v[i]]=dis[u[i]]+w[i]; //對所有的邊進行鬆弛操作
}
}
for(i=1; i<=2*m; i++) {
if(dis[v[i]]>dis[u[i]]+w[i])
return false; //這裡檢測有沒有負權,不過本題用不到
}
cout<<dis[n]<<endl;
}
return 0;
}
看到這裡我想大家一定也發現了一個問題,寫法太樸素了,而且多了好多不必要的計算,那麼,我們就有對它們的優化,首先,先來看一下鄰接矩陣版本的Bellman_Ford的佇列優化,即SPFA 1.0:
/*SPFA相比較於之前的寫法減少了許多不必要的計算,其主體思路為,首先先將起點
放入佇列,然後將它拿出,對其他點進行鬆弛,如果鬆弛成功,則將其放進佇列(前提是它不在佇列中)
每一次都把隊首的點拿出來進行鬆弛,直到佇列為空,注意,點可以重複入隊,如果入隊超過n次,說明有
負權*/
#include <iostream>
#include <queue>
#include <string.h>
using namespace std;
#define inf 0x3f3f3f3f
int m,n;
int map[105][105],dis[105],vis[105],num[105]; //vis表示是否在佇列中,num表示入隊的次數
queue<int> q;
int SPFA(int start)
{
int i,j,k,temp;
for(i=1;i<=n;i++)
dis[i]=inf;
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
dis[start]=0; //將起點初始化為零,保證從這一點開始鬆弛
q.push(start);
while(!q.empty())
{
temp=q.front(); //取隊首
q.pop(); //拿出
for(i=1;i<=n;i++)
{
if(dis[i]>map[temp][i]+dis[temp])
{
dis[i]=dis[temp]+map[temp][i]; //鬆弛
if(!vis[i]) //入隊
{
q.push(i);
vis[i]=1;
num[i]++; //判斷是否存在負環
if(num[i]>n)
return false;
}
}
vis[temp]=0; //標記出隊
}
}
}
int main()
{
int i,j,k;
int a,b,c;
while(cin>>n>>m && n || m)
{
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(i==j)
map[i][j]=0;
else
map[i][j]=inf; //初始化
}
for(i=1;i<=m;i++)
{
cin>>a>>b>>c;
if(map[a][b]>c)
map[a][b]=map[b][a]=c; //無向圖
}
SPFA(1);
cout<<dis[n]<<endl;
}
return 0;
}
雖然說這解決了許多不必要的計算,但是用鄰接矩陣是十分浪費空間的,於是就有了SPFA2.0(鏈式前向星):
/*這種寫法的優勢在於可以節省大量的空間,用到了結構體,會用到一個head
陣列,初始化都為-1,代表沒有點與該點相連,如果有點與它相連,那就讓head
指向它,然後讓這個點的next(結構體變數)指向起點即-1,代表該點是最後一個
和頭點相接的點,如果有新的點進來,就把它插入到兩者之間,讓後進來的點指向
先進來的點,頭點(起點)指向新進來的點,這樣,最後一個點的next值一定為-1
這樣可以對每個邊進行鬆弛*/
#include <iostream>
#include <queue>
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int dis[105],head[105],vis[105];
int n,m,ans;
struct node {
int v,w,next; //v為要到達的點,w為權值,next為指向的下一個點
} edge[2*max+5]; //無向圖 *2
void add(int from,int to,int cost) {
edge[ans].v=to;
edge[ans].w=cost; //新增新的點
edge[ans].next=head[from]; //ans代表第幾條邊
head[from]=ans++; //head指向最新的點(第幾條邊)
}
bool spfa(int start) {
for(int i=1; i<=n; i++)
dis[i]=inf;
dis[start]=0; //除源點外,其他均為inf
for(int i=1; i<=n; i++)
vis[i]=0;
queue<int> q;
q.push(start);
vis[start]=1; //源點入隊
while(!q.empty()) {
int temp=q.front(); //出隊
q.pop();
vis[temp]=0;
for(int i=head[temp]; i!=-1; i=edge[i].next) { //對每個點所指向的點一一進行處理,i=-1時說明已經沒有點與之相連
int a=edge[i].v;
int b=edge[i].w;
if(dis[a]>dis[temp]+b) {
dis[a]=dis[temp]+b;
if(vis[a]==0) {
q.push(a);
vis[a]=1;
}
}
}
}
}
int main() {
int x,y,z;
int i,j,k;
while(scanf("%d%d",&n,&m) !=EOF && n || m) {
ans=1;
for(i=1; i<=n; i++)
head[i]=-1;
for(i=1; i<=m; i++) {
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z); //無向圖
}
spfa(1);
cout<<dis[n]<<endl;
}
return 0;
}
這個一樣可以判斷是否存在負環,我忘記謝了,操作和1.0一樣。
既然Bellman_Ford演算法可以進行優化,那麼Dijkstra一樣進行優化,用到了優先佇列:
/*Dijkstra優先佇列的優化,運用了過載函式可以優先使最大值或者最小值
出佇列的原理進行優化,節省了大量的空間和時間,主體思路為用vector分別
儲存邊和權,用結構體中的元素做跳板,操作過程為,先將起點放入優先佇列(先放到結構體中),
然從該點開始找它可以鬆弛的點,然後全部暫時存到結構體中,然後放入優先佇列中,
然後將起點拿出,優先佇列自動接著會吧最小的拿出來進行鬆弛,直到佇列為空,
注意,一個點只能入隊一次*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int n,m,dis[105],vis[105];
vector<int> e[2*max+10];//邊
vector<int> w[2*max+10];//權 //無向圖 *2
struct P {
int u,d;
bool operator <(const P &a) const {
return d>a.d; //函式過載實現每次優先拿最小值
}
};
void dijkstra(int s) {
int i,k,j;
for(i=1; i<=n; i++)
dis[i]=inf; //初始化
dis[s]=0;
memset(vis,0,sizeof(vis));
priority_queue<P> q;
P tn;
tn.u=s;
tn.d=0; //暫時存到結構體裡面,以便優先佇列發揮作用
q.push(tn);
while(!q.empty()) {
P t;
t=q.top();
q.pop();
if(vis[t.u]) continue; //如果已經訪問過,直接取下一個點
vis[t.u]=1;
int u=t.u;
for(i=0; i<e[u].size(); i++) { //和它相連數量為size
int v=e[u][i];
if(dis[v]>dis[u]+w[u][i]) {
dis[v]=dis[u]+w[u][i];
tn.d=dis[v]; //暫時存到結構體中,以便放入佇列
tn.u=v;
q.push(tn);
}
}
}
}
int main() {
int i,j,k;
int a,b,c;
while(cin>>n>>m && n || m) {
for(i=0; i<=2*m; i++) {
e[i].clear();
w[i].clear(); //一定要初始化,第一次我就因為這個WA了
}
for(i=1; i<=m; i++) {
cin>>a>>b>>c;
e[a].push_back(b);
w[a].push_back(c);
e[b].push_back(a); //無向圖
w[b].push_back(c);
}
dijkstra(1);
cout<<dis[n]<<endl;
}
return 0;
}
同樣, 它也有鏈式前向星的寫法:
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
#define inf 0x3f3f3f
long long dis[10005];
int head[10005];
int cnt;
struct node
{
int v;
int w;
int next;
}edge[20005];
struct tnode
{
int v;//當前點
int w;//當前點到起點的距離
friend bool operator < (tnode a,tnode b)
{
return a.w>b.w;
}
}temp,now;//用於儲存暫時用到的點
void add(int from,int to,int val)//鏈式前向星儲存
{
edge[cnt].v=to;
edge[cnt].w=val;
edge[cnt].next=head[from];
head[from]=cnt++;
}
void dji(int s,int e)
{
priority_queue<tnode> q;
temp.v=s;//存起點
temp.w=0;
q.push(temp);
while(!q.empty())
{
temp=q.top();//每次取距離起點最近的點
q.pop();
int t=temp.v;
if(dis[t]<temp.w) continue;//如果經過一個過渡點到達該點的距離小於起點直接到達該點的距離,那麼已經沒有必要再鬆弛
for(int i=head[t];i!=-1;i=edge[i].next)
{
now.v=edge[i].v;
now.w=edge[i].w;
if(dis[now.v]>dis[temp.v]+now.w)
{
dis[now.v]=dis[temp.v]+now.w;
q.push(now);
}
}
}
}
int main()
{
int t,n,i,j,k;
int a,b,c;
while(cin>>n>>t)
{
cnt=1;
memset(head,-1,sizeof(head));
memset(dis,0x3f,sizeof(dis));
dis[n]=0;
while(t--)
{
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dji(1,n);
cout<<dis[n]<<endl;
}
return 0;
}