迪傑斯特拉算法——PAT 1003


 本文主要是將我對於我對於迪傑斯特拉算法的理解寫出來,同時通過例題來希望能夠加深對於算法的理解,其中有錯誤的地方希望大家指正。

迪傑斯特拉算法

我將這個算法理解成一個局部到整體的算法,這個方法確實越研究就會發現越經典。

首先可以將整個圖的節點看成兩個集合:一個是S,一個是U-S.如果是求v0到圖中各點的最短距離的話,那么S就是已經確認到v0距離最短的點,U-S則是對於整體的點集合U,還沒有加入S集合的點.

這里提出一個算法總體的思想,將所有的點按照一定的原則加入到S集就是解集。而這個解法就是重點了:如果我們能通過圖鄰接矩陣或者已經已知了圖中點與點直連的距離,那么我們可以確定S集合的點就是到v0的最短路徑(注意這個路徑不是兩點之間的距離而是圖中可聯通的任意兩點的距離)。

好了問題縮小為求到v0路徑最短的點,但是還需要注意的是:如果圖中一點v加入到S集合中,圖中其他點由於聯通性就可能會減小到v0的最短路徑。

如果不太清楚就依照圖然后比對,最后再對照算法整理一下思路,如下是示例圖(參考):

                     

算法步驟:

a.初始時,S只包含源點,即S{v}v的距離為0U包含除v外的其他頂點,即:U={其余頂點},若vU中頂點u有邊,則<u,v>正常有權值,若u不是v的出邊鄰接點,則<u,v>權值為

b.U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是vk的最短路徑長度)。

c.k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改后的距離值的頂點k的距離加上邊上的權。

d.重復步驟bc直到所有頂點都包含在S中。

 

根據以上的思想整理一下,可以包括兩部分:

1.找到一個未加入到S集合的節點同時到v0的距離還是最小的節點u,將其加入到S集合中。

2.通過v0節點來更新該點到v0的距離.比如沒有加入集合S的點v,如果v到v0的距離大於v到u和v0到u的最短距離就將v到v0的距離更新為新的最短距離。

具體的算法示例為(實例僅是為了說明思想,所以請結合實際情況修改使用):

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 
 5 int edgs[100][100];     //
 6 int dist[MAXNUM];        //v0到各邊的值
 7 int prev[MAXNUM];       //前驅數組 
 8 
 9 void Dijkstra(int v0,int n)
10 {
11     bool S[MAXNUM];        //確定頂點是否在S集合中 
12      
13      for(int i=0;i<n;i++)
14      {
15          dist[i]=edgs[v0][i];    //vo到個點的初值
16          S[i]=false;         //所有節點未加入S
17          if(dist[i] ==MAXINT)
18          {
19              prev[i]=-1;     //沒有前驅節點 
20          }
21          else
22              prev[i]=v0; 
23      } 
24      
25      dist[v0] =0;
26      S[v0]=true;
27      /*
28      求v到v0的最短距離,將v加到S集中 
29      */ 
30      for(int i=1;i<n;i++)
31      {
32          int min    =MAXINT;
33          int u=v0;
34          
35          //找到當前未使用點的DISJ[i]最小值 
36          for(int j=0;j<n;j++)
37          {
38              if((!S[j]) && dist[j]<min) 
39              {
40                  u=j;
41                  min=dist[j]; 
42              } 
43              
44             S[u]=true;   //找到最短路徑  加入頂點
45              
46              //以新加入的節點    更新從 v0觸發到集合V-S的頂點路徑長度
47              for(int j=0;j<n;j++)
48                  if((!S[j]) && dist[u]+edgs[u][j]<dist[j])    //通過新加入節點找到更短路徑 
49                  {
50                      dist[j]=dist[u]+edgs[u][j];
51                      prev[j]=u;     //更新前驅節點 
52                  } 
53          } 
54      } 
55 } 
56 int main()
57 {
58     int n;     //圖的頂點數
59     cin>>n;
60      for(int i=0;i<n;i++)
61          for(int j=0;j<n;j++)
62              cin>>edgs[i][j];     //邊的權值
63              
64       
65     return 0;
66 } 

 

 

  好了這是我見到的最傳統的寫法(書上基本都這么寫),我上面提到了算法實現其實分為兩部分,聰明的你可否想到:能否調換兩個部分的順序:先通過比較路徑的大小再來更新S集合中的點,之前我確實沒有考慮過類似的問題,但是這次通過看了一些代碼和思索發現這樣是可以的而且不失為一種好的方法。下面通過一道算法題(選自https://www.patest.cn/contests/pat-a-practise/1003)來將兩種思想通過代碼的方式列舉一下。

 

1003. Emergency (25)

 

As an emergency rescue team leader of a city, you are given a special map of your country. The map shows several scattered cities connected by some roads. Amount of rescue teams in each city and the length of each road between any pair of cities are marked on the map. When there is an emergency call to you from some other city, your job is to lead your men to the place as quickly as possible, and at the mean time, call up as many hands on the way as possible.

Input

Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (<= 500) - the number of cities (and the cities are numbered from 0 to N-1), M - the number of roads, C1 and C2 - the cities that you are currently in and that you must save, respectively. The next line contains N integers, where the i-th integer is the number of rescue teams in the i-th city. Then M lines follow, each describes a road with three integers c1, c2 and L, which are the pair of cities connected by a road and the length of that road, respectively. It is guaranteed that there exists at least one path from C1 to C2.

Output

For each test case, print in one line two numbers: the number of different shortest paths between C1 and C2, and the maximum amount of rescue teams you can possibly gather.
All the numbers in a line must be separated by exactly one space, and there is no extra space allowed at the end of a line.

Sample Input

5 6 0 2

1 2 1 5 3

0 1 1

0 2 2

0 3 1

1 2 1

2 4 1

3 4 1

Sample Output

2 4

對照本題,其實可以根據題意可以很快的轉化到以圖為模型的點與點之間的求解。對於我來說基本就確定用迪傑斯特拉算法來AC.將本體的元素抽象一下可以把每一個城市看成一個點,同時城市與城市之間的路可以看成邊,邊有對應的權值大小。有一些細節上的改動,比如:需要我們確定最短邊的個數以及相應的一些點的權值的計算。好我們同樣依照上面的算法來解決。下面是我的算法(已經通過測試),主要上面提到的兩個主要部分:

 

  1 /*
  2 author :LY   2016.5.31
  3 */
  4 #include<iostream>
  5 using namespace std;
  6 #define INF 9999999
  7 
  8 int N;     //number of city
  9 int M;     //number of road    
 10 int start,en;
 11 const int MAXNUM=500;     //最大的頂點數 
 12 
 13 int city[MAXNUM]; 
 14 int path[MAXNUM][MAXNUM];    //
 15 int dist[MAXNUM];        //v0到各邊的最小值 
 16 int pathcount[MAXNUM];    //最短邊的數量 
 17 int amount[MAXNUM];      //可獲取的最大資源數 
 18 bool S[MAXNUM];       //確定頂點是否在S集合中 
 19 
 20 void Dijs(int v0)     //根據dij來確定兩點之間最短 
 21 {
 22          //將v0加入S
 23     dist[v0]=0;
 24     S[v0]=true; 
 25     amount[v0]=city[v0]; 
 26      int u=v0;
 27     
 28     for(int i=0;i<N;i++)
 29     { 
 30         dist[i]=path[v0][i];    //optial
 31         if(dist[i]!=INF && i!=v0)
 32         amount[i]=amount[v0]+city[i]; 
 33            
 34        }
 35 
 36     
 37     /*
 38      求v到v0的最短距離,將v加到S集中 
 39      */ 
 40     for(int i=1;i<N;i++) 
 41     { 
 42      
 43               //求出v0到v的最短路徑,將此點加入到S中
 44          int min=INF;
 45          
 46          for(int j=0;j<N;j++)  //找到最小值 
 47          {
 48              if(!S[j]&&dist[j]<min)
 49              {
 50                  u=j;
 51                  min=dist[j]; 
 52              } 
 53          } 
 54          
 55          S[u]=true;    //將v點加入S 
 56          
 57         //更新v0到各點的路徑長度
 58          for(int j=0 ;j<N; j++)
 59          {
 60              if(!S[j]&&dist[j]>dist[u]+path[u][j])     
 61              {
 62                  dist[j]= dist[u]+path[u][j];   //    更新路徑
 63                 amount[j]=amount[u]+city[j];
 64                 pathcount[j]=pathcount[u];       
 65              } 
 66              else if(!S[j]&&dist[j]==dist[u]+path[u][j])
 67              {
 68                  pathcount[j]+=pathcount[u];
 69                  
 70                  if(amount[j]<city[j]+amount[u])
 71                  {
 72                      amount[j]=city[j]+amount[u];
 73                  } 
 74              } 
 75          }     
 76          
 77     }
 78 } 
 79 int main()
 80 {
 81         
 82     scanf("%d",&N);
 83     scanf("%d",&M);
 84     scanf("%d",&start);
 85     scanf("%d",&en);
 86      
 87     for(int i=0;i<N;i++)
 88     {
 89         scanf("%d",&city[i]); 
 90     } 
 91     
 92     int i=0,j=0;
 93     //初始化dij數組
 94     for(i=0;i<N;i++)
 95     {
 96         dist[i]=INF;
 97         pathcount[i]=1;
 98         S[i]=false; 
 99         amount[i]=city[i]; 
100         for(j=0;j<N;j++)
101         {
102             path[i][j]=INF; 
103         } 
104     }
105      
106     int k1=0,k2=0,k3=0;
107     for(int j=0;j<M;j++)
108     {
109         scanf("%d",&k1);
110         scanf("%d",&k2);
111         scanf("%d",&k3);
112         path[k1][k2]=k3;     //the  length  of road between C1 and C2
113         path[k2][k1]=k3; 
114     } 
115     Dijs(start); 
116     
117     printf("%d %d\n",pathcount[en],amount[en]); 
118     return 0;
119 } 

 

  我提到了可以調換順序來實現,首先通過先比較的路徑,再將點加入到S集合中,不過思路需要很清晰,以下是代碼,對照一下可以加深對於程序的理解:

 1 #include<iostream> 
 2 #include <cstdio>
 3 #define MAX 500
 4 #define INF 999999999
 5 int dist[MAX];
 6 bool mark[MAX];
 7 int mp[MAX][MAX];
 8 int teams[MAX];
 9 int amount[MAX];
10 int pathcount[MAX];
11 int n, m, start, en;
12 
13 void dijkstra(int s){
14     dist[s] = 0;
15     amount[s] = teams[s];
16     mark[s] = true;
17     int newP = s;
18     while (newP != en){
19         for (int i = 0; i < n; i++){
20             if (mark[i] == false){
21                 if (dist[i] > dist[newP] + mp[newP][i]){
22                     dist[i] = dist[newP] + mp[newP][i];
23                     amount[i] = amount[newP] + teams[i];
24                     pathcount[i] = pathcount[newP];
25                 }
26                 else if (dist[i] == dist[newP] + mp[newP][i]){
27                     pathcount[i] += pathcount[newP];
28                     if (amount[i] < amount[newP] + teams[i])
29                         amount[i] = amount[newP] + teams[i];
30                 }
31             }
32         }    
33         int dmin = INF;
34         for (int i = 0; i < n; i++){
35             if (mark[i] == false && dist[i] < dmin){
36                 dmin = dist[i];
37                 newP = i;
38             }
39         }
40         mark[newP] = true;
41     }
42 }
43 
44 int main(){
45     scanf("%d%d%d%d", &n, &m, &start, &en);
46     for (int i = 0; i < n; i++){
47         scanf("%d", &teams[i]);
48     }
49     for (int i = 0; i < n; i++){
50         dist[i] = INF;
51         pathcount[i] = 1;
52         mark[i] = false;
53         for (int j = 0; j < n; j++)
54             mp[i][j] = INF;
55     }
56     for (int i = 0; i < m; i++){
57         int c1, c2, L;
58         scanf("%d%d%d", &c1, &c2, &L);
59         mp[c1][c2] = mp[c2][c1] = L;
60     }
61     dijkstra(start);
62     printf("%d %d\n", pathcount[en], amount[en]);
63     return 0;
64 }

 

由於本人水平有限,參考了一些大神的博文:

http://blog.csdn.net/xtzmm1215/article/details/39006079

http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html

  希望大家批評指正!

 

  日進一小步,月過一大步~~加油!!!

 


免責聲明!

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



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