例題:HDU 2544
最短路
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 89730 Accepted Submission(s): 38892
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 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
Sample Output
3
2
1),深度或廣度優先搜索算法(解決單源最短路徑)
從起始結點開始訪問所有的深度遍歷路徑或廣度優先路徑,則到達終點結點的路徑有多條,取其中路徑權值最短的一條則為最短路徑。
給定一個帶權有向圖G=(V,E),其中每條邊的權是一個實數。另外,還給定V中的一個頂點,稱為
源。
現在要計算從源到其他所有各頂點的最短路徑長度。這里的長度就是指路上各邊權之和。這個問題通
常稱為單源最短路徑 [1] 問題。
從起始結點開始訪問所有的深度遍歷路徑或廣度優先路徑,則到達終點結點的路徑有多條,取其中路
徑權值最短的一條則為最短路徑
下面是核心代碼:
//題意:求1->n的最短路徑
#include<iostream>
#include<string.h>
#define inf 99999999
using namespace std;
int dis[111][111];
bool vis[111];
int n,cnt;//n為節點數,cnt為最短長度
void init(int x){
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++)
dis[i][j]=inf;
dis[i][i]=0;
vis[i]=0;
}
}
void dfs(int st,int dst)
{
if(dst>cnt)return ;//距離大於最短路徑,無需遍歷
if(st==n){//到達終點
cnt=cnt>dst?dst:cnt;
return;
}
for(int i=1;i<=n;i++)
{
if(!vis[i]&&dis[st][i]!=inf&&dis[st][i]){
vis[i]=1;
dfs(i,dst+dis[st][i]);
vis[i]=0;
}
}
}
int main()
{
int m;
while(~scanf("%d%d",&n,&m)&&n&&m)
{
int x,y,len;
cnt=inf;
init(n);
while(m--){
scanf("%d%d%d",&x,&y,&len);
dis[x][y]=min(dis[x][y],len);//兩點之間距離重復輸入取小距離
dis[y][x]=dis[x][y];
}
vis[1]=1;
dfs(1,0);
printf("%d\n",cnt);
}
return 0;
}
Sample Input 2
5 14
2 2 262
5 3 403
4 2 456
1 5 289
3 1 1000
2 4 217
2 5 536
2 5 415
2 4 880
3 1 179
3 4 972
5 3 2
1 3 491
4 1 872
0 0
Sample Output 2
181
2),弗洛伊德算法(解決多源最短路徑):時間復雜度O(n^3),空間復雜度O(n^2)
基本思想:最開始只允許經過1號頂點進行中轉,接下來只允許經過1號和2號頂點進行中轉......允許經過1~n號所有頂點進行中轉,來不斷動態更新任意兩點之間的最短路程。即求從i號頂點到j號頂點只經過前k號點的最短路程。
//題意:求1->n的最短路徑
#include<iostream>
#include<string.h>
#define inf 99999999
using namespace std;
int n,dis[111][111];
void init(){
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++)
dis[i][j]=inf;
dis[i][i]=0;
}
}
int main()
{
int m;
while(~scanf("%d%d",&n,&m)&&n&&m)
{
init();
while(m--){
int x,y,len;
scanf("%d%d%d",&x,&y,&len);
dis[x][y]=min(dis[x][y],len);
dis[y][x]=dis[x][y];
}
for(int k=1;k<=n;k++)//要經過的點
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
printf("%d\n",dis[1][n]);//可以選任意兩點之間的距離
}
return 0;
}
Sample Input 2
5 14
2 2 262
5 3 403
4 2 456
1 5 289
3 1 1000
2 4 217
2 5 536
2 5 415
2 4 880
3 1 179
3 4 972
5 3 2
1 3 491
4 1 872
0 0
Sample Output 2
181
3),迪傑斯特拉算法(解決單源最短路徑)
基本思想:每次找到離源點(如1號結點)最近的一個頂點,然后以該頂點為中心進行擴展,最終得到源點到其余所有點的最短路徑。
基本步驟:1.開容器v,儲存子節點、距離、花費;2、開數組dis記錄起始點到各點距離;3、進行n-1次松弛操作(先找出未標記點中離起始點最近的點,標記該點,然后求出該點子節點到起始點的最短距離(優先)與最短花費);4、輸出到終點的最短距離與花費;
//題意:求兩點之間最短路徑
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
#define N 999999999
using namespace std;
struct node{
int er,len,cost;
};
vector<node>v[1111];
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m)&&n&&m)
{
int dis[1111],spend[1111];
bool vis[1111];
node tmp;
int x,y;
for(int i=0;i<1111;i++)
v[i].clear();
while(m--){
scanf("%d%d%d%d",&x,&y,&tmp.len,&tmp.cost);
tmp.er=x;
v[y].push_back(tmp);
tmp.er=y;
v[x].push_back(tmp);
}
scanf("%d%d",&x,&y);//起點和終點
for(int i=1;i<=n;i++){
vis[i]=0;
dis[i]=spend[i]=N;
}
for(int i=0;i<v[x].size();i++){
dis[v[x][i].er]=v[x][i].len;
spend[v[x][i].er]=v[x][i].cost;
}
vis[x]=1;
for(int k=1;k<=n-1;k++)
{
int id,mi=N;
for(int i=1;i<=n;i++){
if(!vis[i]&&dis[i]<mi){//查詢並記錄離x最近的點
id=i;mi=dis[i];
}
}
vis[id]=1;//標記過的點已經是最短
for(int i=0;i<v[id].size();i++)
{
int vv=v[id][i].er;
if(!vis[vv]&&dis[vv]>dis[id]+v[id][i].len)//未標記、直接距離大於通過id點的距離
dis[vv]=dis[id]+v[id][i].len,
spend[vv]=spend[id]+v[id][i].cost;
else if(!vis[vv]&&dis[vv]==dis[id]+v[id][i].len&&spend[vv]>spend[vv]+v[id][i].cost)//未標記、距離相等找花費更小的
spend[vv]=spend[id]+v[id][i].cost;
}
}
printf("%d %d\n",dis[y],spend[y]);
}
return 0;
}
/*
3 2
1 2 5 6
2 3 4 5
1 3
3 2
1 3 5 6
2 1 3 5
3 2
9 11
8 11
*/
4),Bellman-Ford算法(解決負權邊,解決單源最短路徑,前幾種方法不能求含負權邊的圖)::時間復雜度O(nm),空間復雜度O(m)
主要思想:對所有的邊進行n-1輪松弛操作,因為在一個含有n個頂點的圖中,任意兩點之間的最短路徑最多包含n-1邊。換句話說,第1輪在對所有的邊進行松弛后,得到的是從1號頂點只能經過一條邊到達其余各定點的最短路徑長度。第2輪在對所有的邊進行松弛后,得到的是從1號頂點只能經過兩條邊到達其余各定點的最短路徑長度,......
以下是圖示:
此外,Bellman_Ford還可以檢測一個圖是否含有負權回路:POJ1860
/*
題意:有多種匯幣,匯幣之間可以交換,這需要手續費,當你用100A幣
交換B幣時,A到B的匯率是29.75,手續費是0.39,那么你可以得到
(100 - 0.39) * 29.75 = 2963.3975 B幣。問s幣的金額經過交換最終
得到的s幣金額數能否增加
貨幣的交換是可以重復多次的,所以我們需要找出是否存在
正權回路,且最后得到的s金額是增加的
怎么找正權回路呢?(正權回路:在這一回路上,頂點的權值能不斷增加即能一直進行松弛)
分析:
反向利用Bellman-Ford算法
單源最短路徑算法,因為題目可能存在負邊,所以用Bellman Ford算法,
原始Bellman Ford可以用來求負環,這題需要改進一下用來求正環
一種貨幣就是圖上的一個點
一個“兌換點”就是圖上兩種貨幣之間的一個兌換環,相當於“兌換方式”M的個數,是雙邊
唯一值得注意的是權值,當擁有貨幣A的數量為V時,A到A的權值為K,即沒有兌換
而A到B的權值為(V-Cab)*Rab
本題是“求最大路徑”,之所以被歸類為“求最小路徑”是因為本題題恰恰
與bellman-Ford算法的松弛條件相反,求的是能無限松弛的最大正權路徑,
但是依然能夠利用bellman-Ford的思想去解題。
因此初始化d(S)=V 而源點到其他店的距離(權值)初始化為無窮小(0),
當s到其他某點的距離能不斷變大時,說明存在最大路徑
*/
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
struct node
{
int x,y;
double r,c;
}num[222];
int n,m,s,ans;
double v;
void add(int x,int y,double r,double c)
{
num[ans].x=x;
num[ans].y=y;
num[ans].r=r;
num[ans].c=c;
ans++;
}
bool bellon()
{
double dis[111];
for(int i=0;i<=n;i++)
dis[i]=0;
dis[s]=v;
for(int j=1;j<n;j++)
{
bool flag=0;
for(int i=0;i<ans;i++){
if(dis[num[i].y]<(dis[num[i].x]-num[i].c)*num[i].r)
dis[num[i].y]=(dis[num[i].x]-num[i].c)*num[i].r,flag=1;
}
if(!flag)
return 0;
}
for(int i=0;i<ans;i++)
if(dis[num[i].y]<(dis[num[i].x]-num[i].c)*num[i].r)
return 1;
return 0;
}
int main()
{
int a,b;
ans=0;
double ra,rb,ca,cb;
scanf("%d%d%d%lf",&n,&m,&s,&v);
while(m--)
{
scanf("%d%d%lf%lf%lf%lf",&a,&b,&ra,&ca,&rb,&cb);
add(a,b,ra,ca);
add(b,a,rb,cb);
}
if(bellon())
printf("YES\n");
else
printf("NO\n");
return 0;
}