算法(六)最短路徑之Dijkstra算法

前言

在上一篇博客中,我們學習了最短路徑系列的第一種算法Floyd-Warshall算法來求解圖中點與點之間的最短路徑的問題。這篇博客我們就要來學習一下,求解單源最短路徑的一種算法:Dijkstra算法。

具體問題

還是幾個城市間的最短路徑問題,這次我們需要求的是1號頂點到其餘個點的最短路徑。路徑圖如下
<Image_2>
現在我們需要求解1號點到2、3、4、5、6號點的最短路徑。

Dijkstra算法

          圖的數據化
       同樣通過一個二維數組來保存圖的數據
       <Image_3>
      (和上篇博文相同,1到1爲0表示是同一個點,2到1是無窮符號,表示兩個點暫時沒有路徑聯通,1到2是1,表示1到2的距離是1)
        算法分析過程
      首先,我們求解的是1號點到其餘個點的距離,所以我們可以用一個一維數組來保存這些數據
    int[] dis = {0,0,1,12,999999,999999,999999};
      明明是一共6個點,我們這裏爲什麼是七個數據呢?那是爲了便於編碼,我們默認dis[0]是沒有意義的,所以編碼時,從dis[1]開始,dis[1] = 0,表示1到它自己是0,dis[2] = 1,表示1到2的距離是1。
        所以以後我們就把第一個0默認省掉,直接表示爲
   int[] dis = {0,1,12,999999,999999,999999};//省掉了第0個數
       還記得上面的999999表示的是什麼嗎?表示的是暫時這兩點不連通喲,和上篇文章一樣的意思。

       然後,我們講此時的dis數組中的數稱爲“預估值”,既然是1號點到其餘個點的最短路徑,那麼就需要先找到一個離1號點最近的點,也就是2號點,那麼1號點到2號點的距離有沒有可能通過借道其他點的形式變短呢?因爲我們兩點之間的路徑都是正數,也就是說完全沒有可能通過借道其他點的方式縮短1到2的距離,因爲2已經是距離1最近的點了。所以此時的dis[2] = 1 ,就從預估值,變成了“確定值”,無法也不需要再更改了。
       然後,1到3、4、5、6這幾點,有沒有可能通過借道2點,然距離變短呢?當然完全是有可能的,,比如1到3,只要1到2和2到3的距離相加小於1到3,那麼就可以變短,所以現在就是比較dis[3] 和dis[2] + paths[2][3]的大小就行了,paths[1][2] + paths[2][3] = 1 + 9 = 10,是比dis[3] = 12要小的,所以通過借道2點,我們的dis數組可以更新爲如下。
    int[] dis = {0,1,10,4,999999,999999};
    
      這時候,我們需要繼續思考,除了確定值外的點,距離1號點最近的是4號點,那麼有沒有可能通過借道其他點,來縮短1到4的距離呢顯然是沒有的,因爲已經借道過2點了,然後不管是借道3、5、6點都比現在的1到2到4的距離要遠,所以dis[4] = 4 現在就變成了確定值。
    int[] dis = {0,1,10,4,999999,999999};
      然後我們需要繼續借道4來縮短1到其他點的距離
    int[] dis = {0,1,8,4,17,19};
      此時1到3就變成了1到2到4到3 = 1 + 3 + 4 = 8   1到4 就是1到2到4 = 1 + 3 = 4  1到5 就是1到2到4到5 = 1 + 3 +13 = 17    1到6 就是1到2到4到6 = 1 + 3 +15 = 19

     然後我們需要 從剩下的預估值8、17、19中選出最短的,此時的8已經沒法繼續縮短了,所以也變成了確定值,理由和上面的相同
    int[] dis = {0,1,8,4,17,19};
    然後從5、6號點鐘選出離1號點最近的點,由預估值變成確定值
    int[] dis = {0,1,8,4,13,19};
    然後看1到6能否藉助5變短,1到5現在是13  5到6是4,所以1到6就變成了1到5到6 = 13 + 4 = 17
    int[] dis = {0,1,8,4,13,17};
     
     因爲到了6之後,已經沒有其他的點了,所以現在1到2、3、4、5、6個點的最短距離就確定是上面這個數組中的值了。所以我們就完成了1點到其他各個點的最短距離的計算工作。

    上述的分析過程,其實就是Dijkstra算法的基本思想,讓我們來總結一下,他是如何通過哪幾步來完成計算的。
         1、將所有頂點分成兩部分:已知最短路程的頂點集合P和未知最短路徑的頂點集合Q。也就是上面我們的確定值集合和預估值集合。在算法開始的時候,P集合中只有源點一個點。我們這裏可以通過一個數組mark來標記哪些點是確定的,哪些點是預估的,確定的點 mark[i] = 1 ,預估的點 mark[i] = 0 。
         2、然後源點到自己的距離我們設爲0 ,如果有其他點能之間到源點,則設置爲dis[i] = paths[i][s] ,如果不能之間到達就標記爲 無窮(999999)。
         3、在集合Q中,選擇一個距離源點最近的點,加入到集合P中,然後考察集合Q中的剩餘點,能否藉助已知最短路徑的點,來縮短到源點的距離。
         4、重複第3步,直到集合Q中的元素爲0個,算法結束,最終dis數組中的值,就是源點到各個點的最短路徑。

     代碼編寫
     分析完成之後,我們就可以進行代碼編寫了。
    Dijkstra算法代碼如下
   public void dijkstra(int[][] paths,int n){
        int[] dis = new int[n + 1];
        //初始化dis數組
        for (int i = 1; i <= n; i++) {
            dis[i] = paths[1][i];
        }
        //初始化mark數組
        int[] mark = new int[n +1];
        for (int i = 1; i <= n; i++) {
            mark[i] = 0;
        }
        mark[1] = 1;
        while (!isEnd(mark)){
            //先比較所以mark = 0 的值,將最小的那個變成確定值,並且根據這個值來更新dis數組
            int min = 999999;
            int minNum = 1;
            for (int i = 1; i <= n; i++) {
                if (mark[i] == 0 && dis[i] < min){
                    min = dis[i];
                    minNum = i;
                }
            }
            //將這個值變成確定值
            mark[minNum] = 1;
            //並且根據這個確定值來縮小其他dis數組中的值
            for (int i = 1; i <= n; i++) {
                if (dis[i] > dis[minNum] + paths[minNum][i]){
                    dis[i] = dis[minNum] + paths[minNum][i];
                }
            }
        }
        StringBuilder bu = new StringBuilder();
        for (int i = 1; i <= n; i++) {
            bu.append("  "+dis[i]);
        }
        Log.i("hero","---dis == "+bu.toString());
    }
    private boolean isEnd(int[] mark){
        for (int i = 1; i < mark.length; i++) {
            if (mark[i] == 0){
                return false;
            }
        }
        return true;
    }
    isEnd方法,是用來判斷估算值集合是否還有數,以便於結束算法的方法.。
   調用代碼如下
    public void shortestPath(){
        //初始化城市地圖    
        int[][] paths = new int[7][7];//便於理解,多初始化了一個數
        paths[1][1] = 0; paths[1][2] = 1;paths[1][3] = 12;paths[1][4] = 999999;paths[1][5] = 999999;paths[1][6] = 999999;
        paths[2][1] = 999999;paths[2][2] = 0;paths[2][3] = 9;paths[2][4] = 3;paths[2][5] = 999999;paths[2][6] = 999999;
        paths[3][1] = 999999; paths[3][2] = 999999; paths[3][3] = 0;paths[3][4] = 999999;paths[3][5] = 5;paths[3][6] = 999999;
        paths[4][1] = 999999; paths[4][2] = 999999; paths[4][3] = 4;paths[4][4] = 0;paths[4][5] = 13;paths[4][6] = 15;
        paths[5][1] = 999999; paths[5][2] = 999999; paths[5][3] = 999999;paths[5][4] = 999999;paths[5][5] = 0;paths[5][6] = 4;
        paths[6][1] = 999999; paths[6][2] = 999999; paths[6][3] = 999999;paths[6][4] = 999999;paths[6][5] = 999999;paths[6][6] = 0;
        dijkstra(paths,6);
    }
    
     代碼運行結果
<Image_4>
    從結果可以看出,跟我們的分析結果一模一樣。 這個算法的時間複雜度是O(N * N)。

總結

        到這裏呢,Dijkstra算法也就學習完畢了,記得嘗試自己不看源碼完成代碼的編寫喲。這樣才能確定你是否是真的學會了。雖然算法的名字都比較難記,但是他們的思路其實都是很清晰的。下一篇我們就學習一種便於計算最短路徑的數據結構:鄰接表以及第三種求解最短路徑的方法Bellman-Ford算法。
        因個人水平有限,上文難免有錯誤和遺漏,請大家指正批評
        一同學習,一同進步吧





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章