最短路徑四種方法


例題: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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM