算法與數據結構(六) 迪傑斯特拉算法的最短路徑(Swift版)


上篇博客我們詳細的介紹了兩種經典的最小生成樹的算法,本篇博客我們就來詳細的講一下最短路徑的經典算法----迪傑斯特拉算法。首先我們先聊一下什么是最短路徑,這個還是比較好理解的。比如我要從北京到濟南,而從北京到濟南有好多條道路,那么最短的那一條就是北京到濟南的最短路徑,也是我們今天要求的最短路徑。

因為最短路徑是基於有向圖來計算的,所以我們還是使用上幾篇關於圖的博客中使用的示例。不過我們今天博客中用到的圖是有向圖,所以我們要講上篇博客的無向圖進行改造,改成有向圖,然后在有向圖的基礎上給出最小生成樹的解決方案。本篇博客我們的思路與之前數據結構相關博客的風格保持一致,首先我們先給出迪傑斯特拉算法的原理以及詳細的示意圖,然后根據這些示意圖給出具體的實現方案。

 

一、迪傑斯特拉算法原理解析

在博客的第一部分,我們不談任何與代碼相關的內容,只談迪傑斯特拉算法的原理以及生成最短路徑的具體步驟。下方我們會給出迪傑斯特拉算法的計算最短路徑的每一步,並給出每一步具體的說明。廢話少說,進入本部分的主題。

 

1、有向帶權圖

本篇博客依然采取我們之前用的圖的結構。不過我們本篇博客使用的是有向圖。下方這張圖就是是我們之前使用的無向圖轉換過來的,只是給之前的圖的邊添加的具體的方向,其他的並為改動。由無向圖轉換后的有向圖如下所示,我們將在下方的圖的基礎上計算出從A到D的最短路徑

  

2.迪傑斯特拉算法的具體步驟

下圖就是求上圖中A->D的最短路徑時使用迪傑斯特拉算法的具體步驟。迪傑斯特拉算法主要核心思想是由起始結點開始,慢慢的由尾結點擴散。直到擴展到尾結點位置。下方我們將根據每個步驟給出具體的介紹:

  

 

  • (1)與起始結點A直接相連的點是B和F, 即 A->B的距離為10A->F的距離為11, 所以我們選擇A->B這個路徑。
  • (2)選擇B結點后,我們開始探測與B相連的結點,即 A->B->G路徑長度為26A->B->I路徑長度為22A->B->C路徑長度為28,這三個與上一步留下的 A->F(11)相比,A->F(11)的距離最短,所以接下來要探測與F相連的結點。
  • (3)F可到達的點是G和E,所以 A->F->G的距離為27, A->F->E的距離為37。因為 A->F->G(27) 大於 A->B->G(26)這個路徑,所以由A到G的路徑我們依然選擇A->B->G(26)這個路徑。從 A->B->G(26),A->B->I(22),A->B->C(28), A->F->E(37)。中選出最短那個距離就是 A->B->I(22),所以我們將I作為我們下次探測的結點。
  • (4)與I相連的就是D結點,所以我們容易計算出 A->B->I->D這條路徑的長度為 22+21=43。從 A->B->I->D(43), A->B->G(26), A->B->C(28), A->F->E(37)這些路徑中我們還是選擇最小的那一個,不難看出 A->B->G(26)這條路徑在上述路徑集合中最小,所以我們將G結點作為下次路徑的探測對象。
  • (5)G可到達的結點是H和D, 所以我們可以得到兩條新的路徑 A->B->G->H(26+19=45)A->B->G->D(26+24=50),因為 A->B->G->D(50)這條路徑的長度要大於 A->B->I->D(43)這條路徑的長度,因為這兩條路徑都是從A到D,所以我們選擇較小的 A->B->I->D(43)路徑。從 A->B->G->H(45),A->B->I->D(43), A->B->C(28), A->F->E(37)這幾條路徑中我們依然選擇最小的那條路徑。從上面這幾條數據我們不難看出  A->B->C(28)這條路徑最短,所以我們下次要探測的點是C點。
  • (6)C點可以到達的點只有D點,所以我們可以得到一條新的路徑 A->B->C->D, 路徑長度為50。因為從A到達D的路徑還有 A->B->I->D(43),而 A->B->C->D(50)這條路徑的長度要大一些,A到D點的路徑依然選擇 A->B->I->D(43)。C結點探測完畢,我們從 A->B->G->H(45),A->B->I->D(43), A->F->E(37)幾條候選路徑中依然選擇最小的那一個。不難看出  A->F->E(37)這條路徑最小,所以下一步我們要探測E結點。
  • (7)E結點可以到達的點為H和D,所以 A->F->E(37)這條路徑可以延伸為 A->F->E->H(44)和A->F->E->G(57)兩條邊。因為A到H的路徑還有一條為 A->B->G->H(45),而我們剛生出的這一條要小於之前的那一條,所以A到H的路徑更新為 A->F->E->H(44)。而A->F->E->G(57)這條A到D的路徑要比 A->B->I->D(43)要大,所以不進行更新,A到D的路徑依然采用A->B->I->D(43)。經過這一步后我們將 A->F->E->H(44)和A->B->I->D(43)進行比較,較小的路徑為 A->B->I->D(43),而D節點又是我們的終點, 所以A到D的最短路徑為A->B->I->D(43)

 

3、迪傑斯特拉算法的數據表示

上面是我們使用圖形的方式給出了迪傑斯特拉算法的具體步驟,接下來我們會把上面的步驟轉換成數據的表示方式,以便於我們使用程序進行計算。下方就是上面這些示意圖的的完整的數據表示。下方矩陣中的數據標志着起始節點到該節點的距離。由上到下,以此增加距離的大小,而最上面一排的紅色字母,標示着該結點的前驅。下方我們在給出每一個行數據的詳細介紹。

  • 第一行數據記錄了A->B和A->F的信息,當然A->A的距離為0。其他尚不可達的點為-1。
  • 第二行在第一行數據的基礎上證據了 A->B->C, A->B->G, A->B->I的距離信息,因為從第一行數的比較我們可以知道,A->B的距離要小於A->F的距離,所以我們的最短路徑要向着A->B->?上發展。
  • 第三行的數據則是在第二行的數據上得到的,從第二行數據上我們容易看出,A->F(11)要比A->B->(C, G, I)都要小,所以我們的最短路徑要向着A->F->?的放心發展。所以我們找到了A->F->E(37)和A->F->G(28)這條路徑。因為 A->B->G(26)小於A->F->G(28),所以A到G的路徑不進行更新。
  • 第四行的數據則是第三行數據的延伸,我們可以知道A->B->I(2)在當前所有延伸的路徑中最小,所以我們要向着 A->B->I->? 方向發展。因此我們找到了 A->B->I->D(43)這條路徑。
  • 以此類推……

 

  

其實上圖就是我們之前原理圖的數據表示,接下來我們就要根據這些原理圖和數據圖來給出我們的代碼實現,當然我們還是使用Swift語言來實現。

 

二、迪傑斯特拉算法的具體實現

1.上述原理總結

上面說這么多,簡單的總結一下,上面整個過程無非就是下面這兩步的循環,而循環結束的條件就是最短路徑延伸到結束路徑即可,也就是我們本例中的D點。

  • 第一步:比較已經發展的所有路徑,找出目前最短的路徑。
  • 第二步:在上一步找到的最短路徑的基礎上發展新的路徑,然后重復上一步,直到延伸到 end節點為止。

經過這么一分析,在給出具體的代碼實現就顯得簡單多了,我們的程序整體上來說就是一個循環,里邊包含着上面這兩步。

 

2.具體代碼實現

分析完原理后,接下來我們就要開始實現我們的代碼了。下方就是我們整個代碼的具體實現。當然,我們的圖結構是以鄰接鏈表的形式存儲的有向聯通圖。具體代碼實現如下所示:

(1)路徑結構的存儲

我們先創建一個DistanceInfo類,該類中記錄了一些距離信息。previousNoteIndex字段存儲的是當前節點的前驅節點的索引,默認為-1。而distance存儲的就是起始結點到該節點的距離,默認是最大距離。而isInPath則標記該結點是否位於已生成路徑的上,如果是的話就是true, 如果不是就是false,默認是false。

  

 

(2)代碼的整體結    

下方代碼塊就是我們迪傑斯特拉算法代碼實現的整體結構。首先我們會創建一個distanceInfos數組,數組中元素的個數就是圖的結點的個數。其中存儲的是DistanceInfo的對象,每個數組索引對應的DistanceInfo對象中存儲的信息就是起始結點到該結點的距離信息。首先我們對起始結點的DistanceInfo對象進行初始化。

緊接着下方這個循環就是我們上面所說的循環,循環結束的條件就是將最短路徑延伸到end結點。循環中主要有兩步,第一步是在當前循環的index對應的結點上發展新的路徑,第二步就是在這些發展的路徑中尋找最短路徑,在這個新最短路徑的基礎上再次發展新的路徑。

  

 

(3)發展新的路徑

接下來我們來看一下countCurrentNoteAllDistance()方法中的代碼,也就是發展新的路徑的代碼。主要就是遍歷鄰接鏈表上當前索引所連的鏈表上的數據,將這些數據所對應的結點添加到我們的路徑上。往路徑上添加新的結點是要注意一點,比如A->D是100, 之前B->D是80,如果現在的路徑要比之前的路徑要大就不更新,反之就更新我們距離信息數組中的DistanceInfo對象。具體代碼如下所示:

  

 

(4)、尋找最小路徑

下方代碼就是尋找當前已發展的所有路徑中最短的那條路徑,主要還是對DistanceInfo數組的操作。下方代碼,找到最短路徑后,並把最短路徑的索引進行返回。

  

上方就是我們迪傑斯特拉算法代碼實現的核心代碼。

 

三、測試用例

實現完代碼后,我們就要對代碼進行測試了。下方就是我們的測試用例,該測試用例中創建的圖是有向連通圖,並且要求出節點A->D的最短路徑。

  

上述測試用例的輸入結果如下:

  

今天的博客就先到這兒,本篇博客所涉及的Demo也將會在github上進行分享,分享地址如下:

github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/ShortestPathDijkstra

 


免責聲明!

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



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