算法(八)最短路徑之Bellman-Ford算法的隊列優化以及幾種最短路徑算法對比

前言

從上篇博客的,Bellman-Ford算法介紹的最後一部分,我們指出,其實,可以對該算法進行進一步的優化。原因是因爲:其實在每一輪鬆弛操作結束後,就會有一些頂點已經求得其最短路徑。此後這些頂點的最短路徑的估計值就會一直保持不變,但是每一次都還要對其進行判斷。這裏浪費了時間,這就啓發了我們每次僅對最短路估計值發生了變化的頂點的所有出邊執行鬆弛操作。所以下面我們學習Bellman-Ford算法的隊列優化。而這篇文章,我們就來介紹一下其隊列優化,以及幾種最短路徑算法的對比。

Bellman-Ford算法的隊列優化

        這個優化算法大致如下:
        每次選取隊列首頂點u,對頂點u的所有出邊進行鬆弛操作,例如有一條u—>v的邊,如果通過u—>v這條邊使得源點到v點的最短路程變短,且頂點v不在當前隊列中,就將頂點放入隊尾。需要注意的是,同一個頂點同時在隊列中出現多次是毫無意義的,所以我們需要一個數組來判重。在對頂點u的所有出邊鬆弛完畢後,就將頂點u出隊。接下來不斷從隊列中取出新的隊首頂點再進行如上的操作,直到隊列空爲止。
        下面我們用一個例子來說明:
<Image_1>       
      最短路徑圖如上。
      具體過程分析
      1、首先我們初始化一個數組dis = {0,999999,999999,999999,999999},表示源點1號點,到1、2、3、4、5點的距離,其中1號點到1號的距離初始化爲0,到其餘各點初始化爲無窮(999999)
      2、然後需要一個記錄隊列que來存放各點 
      3、首先我們來看1號點的各個邊,能否讓1號點到其餘點的距離變短,首先是1—>2 邊,可以讓dis[2] =  2,所以鬆弛成功,看隊列que中是否有2號點,發現沒有,那麼將2號點添加到隊列que中。
          現在數組dis = {0,2,999999,999999,999999} 隊列que = { 1 ,2},然後看1—>5邊,是否可以讓dis[5]變小,我們發現能讓dis[5]變小,然後看隊列que中是否有5號點,沒有 那麼添加進去,現在的數組  dis=  { 0,2,999999,999999,10} 隊列que = {1,2 ,  5}。然後1號點沒有其他出邊了,我們將1號點從que中移除出去,第一個點鬆弛完畢的結果是  dis = {0,2,999999,999999,10}  que = {2,5}。
      4、然後,我們繼續從que隊列的隊首中取出頂點,並且判斷這個頂點的各個出邊是否能讓源點1到其餘個點的距離縮短。首先看頂點2的第一條出邊 2—>3能否讓頂點1到頂點3的路徑變短,也就是dis[2] + 2—>3  ? dis[3]。我們發現dis[2] + 3 < dis[3] = 999999,所以鬆弛成功,然後看頂點3是否在隊列中,不在,所以,現在的dis = {0,2,5,999999,10},隊列 que = {2,5,3 },然後看頂點2的第二條出邊2—> 5 (上面的圖漏了一個2—>5的邊的距離是7),能否讓源點1到頂點5的距離變短,我們發現dis[2] + 2 —>5 = 2 + 7 < dis[5] = 10,所以鬆弛成功,然後看頂點5是否在隊列que中,發現已經存在了,所以不繼續往裏面添加了,所以頂點2的各個出邊的鬆弛結果是 dis = {0,2,5,999999,9}
que = { 5,3}。
       5、依次繼續,直到隊列que中沒有了頂點。這樣就完成了Bellman-Ford算法的隊列優化。

       代碼編寫
       首先,我們初始化dis數組和隊列que
    int[] dis = new int[6];
        for (int i = 1; i <= 5; i++) {
            if (i == 1){
                dis[i] = 0;
            }else {
                dis[i] = 999999;
            }
        }
    Queue<Integer> que = new LinkedList<>();//記錄最短路徑有過變化的點
    que.add(1);//先增加一個源點,算法需要從源點開始
       因爲java中已經有實現好的隊列LinkedList了,所以我們這裏就不手動實現隊列這種數據結構了。dis數組的dis[1] 初始化爲0,其餘點初始化爲無窮,上面已經解釋過了,就不再多解釋。這裏的que.add(1)是提前往隊列中增加一個點,因爲我們需要一個起始點。
       然後是初始化路徑圖
        //初始化路徑圖
        int[] u = new int[8];//邊的起點
        int[] v = new int[8];//邊的終點
        int[] w = new int[8];//邊的權值
        u[1] = 1; v[1] = 2; w[1] = 2;//各條邊的初始化
        u[2] = 1; v[2] = 5; w[2] = 10;
        u[3] = 2; v[3] = 3; w[3] = 3;
        u[4] = 2; v[4] = 5; w[4] = 7;
        u[5] = 3; v[5] = 4; w[5] = 4;
        u[6] = 4; v[6] = 5; w[6] = 5;
        u[7] = 5; v[7] = 3; w[7] = 6;
        //我們使用鄰接表來完成圖的記錄
        int[] first = new int[8];
        int[] next = new int[8];
        for (int i = 1; i <= 7; i++) {
            first[i] = -1;
        }
        for (int i = 1; i <= 7; i++) {
            next[i] = first[u[i]];
            first[u[i]] = i;
        }
         這裏不多說,u,v,w跟以前一樣記錄了一條邊的完整信息,u記錄的是邊的起點,v記錄的是邊的終點,w記錄的是邊的權值。然後我們初始化了兩個數組first和next,這是因爲該算法中用到了遍歷一個頂點的所有出邊。所以我們使用了鄰接表來存儲邊的信息,能優化一下算法的執行時間。
         然後就是算法的主體
    while (!que.isEmpty()){
            //如果隊列不是空的
            //拿到隊列中的第一個點
            Integer remove = que.remove();
            //遍歷所有的頂點remove的出邊
            int k = first[remove];
            while (k != -1){
                if (dis[v[k]] > dis[u[k]] + w[k]){
                    //鬆弛成功
                    dis[v[k]] = dis[u[k]] + w[k];
                    if (!que.contains(v[k])){
                        que.add(v[k]);
                    }
                }
                k = next[k];
            }
     }
      //算法鬆弛完畢,打印結果
     StringBuilder stringBuilder = new StringBuilder();
     stringBuilder.append("Bellman-Ford的隊列優化結果-dis = {");
     for (int i = 1; i <=5 ; i++) {
          if (i == 5){
               stringBuilder.append(dis[i]+"}");
          }else {
            stringBuilder.append(dis[i]+",");
         }
     }
     Log.e("hero","--"+stringBuilder.toString());

      執行結果
<Image_2>

       到這裏,Bellman-Ford算法的隊列優化也差不多結束了。但是我們還有一個點需要注意一下,第一就是,這種優化我們如何判斷一個圖是否有負權迴路呢?其實我們可以通過一個點如果進入了隊列que中n次,說明他肯定是有負權迴路的。該優化的核心就是,只有那些在前一遍鬆弛中丐幫了最短路徑估計值的點,纔可能引起其他領接點的最短路程的估計值發生變化。因此使用一個隊裏que記錄被成功鬆弛的點,之後只對隊列中的點進行處理,這降低了算法的時間複雜度。

最短路徑算法對比分析

      上面,我們學習了Floyd、Dijkstra、Bellman-Ford以及Bellman-Ford的隊列優化等求解最短路徑的算法。他們的一些數據對比如下
<Image_3>

      具體的分析這裏就不再進行了,因爲我們已經分別深入的學習了這四種算法的核心思想和實現。

總結

      這篇文章到這裏,也就結束了。最短路徑的幾種常見和簡單的算法,我們也都已經學習了一遍。當然這個問題,我們只是猜入門,對於更加深入的知識還有很多,有興趣的朋友,可以繼續搜索相關資料進行學習。
      ps:最近看了浮生六記這本書,不得不說,以前的社會生產力低下,衛生醫療條件落後。能安穩一生,最後善終的人,已是上天賜予的莫大幸福了。所以,珍惜如今的生活吧。


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