Dijkstra算法暢遊星際

来源:互联网 发布:cea热力学软件 编辑:程序博客网 时间:2024/05/16 11:33

今天淩晨,不知大家有沒有去看雙子座流星雨。我和我宿舍的哥們兒可是頂著嚴寒都去了的。報紙上說高峰時會達到每分鐘200顆,呵呵,好像沒有這麽誇張。流星不那麽容易被看到,於是我們便只得一直仰著脖子;仰酸了,就只好忍著刺骨的樹膠跑道躺在地上。不過這麽苦還是挺值得的,因爲流星真的好漂亮啊!特別是那種特別大的,有的火紅、有的白紫,隕落的時候照亮了穹隆的西北角;但更多的是那些小的,它們偷偷地、迅速地滑過天際,你反應都還來不及,就別提許願了呵呵!

看得累了,眼睛花了,我就幻想著自己開著星際飛船在每顆星星閒來回地馳騁。嘿,我突然想問你一個很古怪的問題,如果這些星星之間已經用星際隧道連接成了一個網絡,每條隧道的距離以及耗油量都不同,而你只能從星級隧道中飛行,那你應該怎麽尋找從A星到B星的最便捷的路徑呢?嘿嘿,讓我來告訴你思路吧。

實際上,我剛才提出的那個星際模型是一個典型的圖(graph)。什麽是圖呢?圖就是一個有限的集合,包括了一群節點(vertex)和把節點連成一個網絡的帶了權值的邊(edge),數學圖論裏把它記作G=(V,E)。現在來抽象我們的問題吧:我們就是要在一個圖内尋找某一個到另一個節點的路徑,使得沿著這條路徑走經過的邊的權值之和最小。在尋找最短路徑的諸多算法中,最有名的應該是Dijkstra算法了。在我學圖的那堂課上,這個算法讓我細細品味了很久。

下面的代碼摘自我的一份C++作業,其中的圖是用一個二維等長寬數組表示的。我故意刪掉了註釋,看看你能不能自己看懂它。

#include "iostream"
#define VEXNUM 6
#define INFIN 32767

void
Dijkstra(int (*ThisGraph)[VEXNUM], int from, int to) {

 int Dist[VEXNUM], Path[VEXNUM], IsVisited[VEXNUM];

 for(int i = 0; i < VEXNUM; i++) {

  Dist[i] = ThisGraph[from][i];
  IsVisited[i] = 0;
  if(i != from && Dist[i] <INFIN)
   Path[i] = from;
  else
   Path[i] = -1;

 }

 IsVisited[from] = 1;
 Dist[from] = 0;
 for(i = 0; i < VEXNUM - 1; i++) {

  int min = INFIN;
  int u = from;
  for(int j = 0; j < VEXNUM; j++)
   if(!IsVisited[j] && Dist[j] < min) {

    u = j;
    min = Dist[j];

   }
  IsVisited[u] = 1;
  for(int w = 0; w < VEXNUM; w++) {

   if(!IsVisited[w] && ThisGraph[u][w] < INFIN && Dist[u] + ThisGraph[u][w] < Dist[w]) {

    Dist[w] = Dist[u] + ThisGraph[u][w];
    Path[w] = u;

   }

  }

 }

 std::cout<<"從"<<from<<"點到"<<to<<"點的最短路徑為(從右到左輸出):"<<std::endl;
 i = to;
 while(i != from) {

  std::cout<<i<<"<-";
  i = Path[i];

 }
 std::cout<<i;
 std::cout<<std::endl<<"長度為:"<<Dist[to];

}

你結合著看一個例子,這是一個圖的典型的鄰接矩陣:

INFININFIN10INFIN30100
INFININFIN5INFININFININFIN
INFININFININFIN50INFININFIN
INFININFININFININFININFIN10
INFININFININFIN20INFIN60
INFININFININFININFININFININFIN

對上面的圖執行Dijkstra算法,求得的步驟是:
      點         i=1         i=2         i=3         i=4         i=5
        1   INFININFININFININFININFIN
        210  0->2
        3INFIN   60 0->2->350 0->4->3
        430 0->430 0->4
        5100 0->5100 0->590 0->4->560 0->4->3->5
     最短        2          4           3              5
 IsVisited 02024023402345



上面這個例子是查找從0點出發到達所有其他頂點的最短路徑。看懂了嗎?實際上,要求從某個源點到某個頂點的最短距離,必須遍歷圖的所有頂點,把源點到所有頂點的最短距離都求出來才行。上面的Dist數組就是用來存放這所有的最短距離的。Dist初態就是從源點到其它點的直接路徑的權值。但是你也看出來了,大多數情況下,從一個點到另一個點的最短路徑並不是直接路徑,怎麽辦?

因此這裡另外有一個取巧的地方,就是引進了IsVisited這個數組。它實際上是一個集合,如果它包含了i點,那麽IsVisited[i]就標記為1,否則為0。在初態,IsVisited裏標記為1的只有源點,以後每求得一個最短路徑,就往IsVisited集合中增加一個點。當所有頂點都被加到IsVisited集合中,就意味著遍歷完畢,算法結束了。

看懂了IsVisited的作用,你就理解了Dijkstra。我們先看最簡單的情況:和0點直接相鄰的點有2、4、5,並且到2的權值最小,那麽,我們可以打包票說,從0到2之間最短路徑就是這條直接相連的路徑了。那麽,我們可以把2標記為IsVisited了。但是,我們在這一步卻只能斷定這一點,因爲你無法保證從0到4或者5的直接權值一定最小。請你好好回味一下,別急著往下看。我覺得,所有複雜的東西,把它最簡單的情況搞透徹,就弄懂了一大半!

以下的每一步實際上都是第一步最簡單情況的擴展,只是另外多考慮到了之前已放入IsVisited集合裏的頂點。因爲:下一次查找到的最短路徑或者是直接路徑0->2,或者是中間經轉了IsVisited中頂點而到達2點的路徑,反正短的那條就一定是了!你懷疑這句話嗎?如果不是這樣的話,那麽就意味著最短路徑上存在一點不在IsVisited中了。但是別忘了,我們是按照路徑長度遞增順序來產生每個最短路徑的,所以長度比這條路徑短的所有路徑都已經產生了,它們的終點一定在IsVisited中!假設推翻了……你可以大膽地挑出那條最短的路徑,把它的終點放進IsVisited集合裏,繼續下一輪查找。只要圖是連通的,每一次都能找到到達某一個點的最短路徑,那麽查找VEXNUM-1次就OK啦!

説道流星,你可千萬別以爲查找最短路徑只是存在于太空科幻小説中,其實就在我們的地球上,它就有著極其廣闊的運用背景。Dijkstra已經廣泛地應用于AI中——有圖的地方,就有Dijkstra。把Dijkstra算法稍加修改,還能得到另一個很有名的Prim算法,用於建立最小支撐樹。

哎呀差點忘了,我們剛才只是說找到了最短路徑的大小,但是最短路徑經過了哪些頂點還沒求呢!你可能早看出來了,這個Path數組就是專門來干這個的嘿嘿!它保存的是到達每條最短路徑終點的前一個頂點。呵呵,很巧是吧。不過我上面的代碼可以按照從終點到起點的順序把頂點打印出來,你恐怕看了相當不爽——“涕淌君怎麽年紀輕輕就這麽懶!”好吧,就留給你完成吧!

原创粉丝点击