數據結構(十):復雜圖-加權有向圖,最短路徑


 

一、 加權有向圖概述

  加權有向圖是在加權無向圖的基礎上,給邊添加了方向,並且一條加權有向邊只會在一個頂點的鄰接表中出現。

二、 加權有向圖實現

  為了體現邊的有向性,我們需要知道邊的起點和終點,參照如下來構建有向邊,而有向圖的構建只需在前面無向圖的基礎上,將無向邊對象更換為有向邊對象即可

/**

 * 有向邊對象

 * @author jiyukai

 */

public class DirectedEdge {

 

       //有向邊起點

       private int v;

      

       //有向邊終點

       private int w;

      

       //有向邊權重

       private double weight;

 

       public DirectedEdge(int v, int w, double weight) {

              super();

              this.v = v;

              this.w = w;

              this.weight = weight;

       }

      

       /**

        * 起點

        * @return

        */

       public int from() {

              return v;

       }

      

       /**

        * 終點

        * @return

        */

       public int to() {

              return w;

       }

      

       /**

        * 有向邊權重

        * @return

        */

       public Double weight() {

              return weight;

       }

}

 

import com.data.struct.common.list.queue.Queue;

 

/**

 * 有向圖實現

 * @author jiyukai

 */

public class Digraph {

 

       //定點個數

       public int V;

      

       //邊的數量

       public int E;

      

       //圖的鄰接表

       public Queue<DirectedEdge>[] qTable;

      

       public Digraph(int v) {

              this.V = v;

             

              this.E = 0;

             

              //初始化鄰接表,數組中的索引為頂點,值為已隊列,存放相鄰的頂點

              qTable = new Queue[v];

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

                     qTable[i] = new Queue<DirectedEdge>();

              }

       }

      

       /**

        * 向圖中添加一條有向邊

        * @param v

        * @param w

        */

       public void addEdge(DirectedEdge e) {

             

              //獲取邊的起點

              int v = e.from();

             

              //起點到終點的指向

              qTable[v].enqueue(e);

             

              //邊加1

              E++;

       }

      

       /**

        * 返回當前頂點的數量

        * @return

        */

       public int V() {

              return V;

       }

      

       /**

        * 返回當前邊的數量

        * @return

        */

       public int E() {

              return E;

       }

      

       /**

        * 獲取與頂點V相鄰的頂點

        * @param V

        * @return

        */

       public Queue<DirectedEdge> adjoin(int V) {

              return qTable[V];

       }

      

       /**

        * 獲取加權有向圖的所有邊

        * @return

        */

       public Queue<DirectedEdge> directEdges(){

              Queue<DirectedEdge> diedges = new Queue<DirectedEdge>();

             

              //由於有向圖中,有向邊只存在一個頂點的鄰接表中,不存在相同邊出現在不同頂點的鄰接表中的情況,所以遍歷添加即可

              for(int v=0;v<V;v++) {

                     for(DirectedEdge e : qTable[v]) {

                            diedges.enqueue(e);

                     }

              }

             

              return diedges;

       }

}

 

三、 最短路徑

  最短路徑用來在加權有向圖中,尋找頂點v到頂點w經過的有向邊的最短路徑,如下為一幅加權的有向圖,各邊的指向和權限已在圖和表格中標出,紅色邊即為頂點0到頂點4經過的最短路徑。

       

四、 松弛思想和Disjstra實現思路

  那么如何在一副有向圖中求得最短路徑呢,這里我們用到了一種思想叫松弛思想,如下圖

  同樣是頂點0到頂點2的路徑,圖一的藍色邊框看做皮筋,那么皮筋圈住的距離是0.35+0.15=0.5,圖二的藍色邊框看做皮筋,那么皮筋圈住的距離是0.04,同樣是起點為0,終點為2

  我們把圖一的皮筋改成圖二后,皮筋很明顯的松弛了,即在松弛狀態下達到了和圖一一樣的效果。

   

  這種松弛的原理和方法可以用來解決最短路徑的問題,我們在算最短路徑時,需要有個數組edges[]存放當前頂點和上一個頂點的最短邊,索引為頂點,值為邊

  edgesWeight[]存放起點s到其他頂點的權重之和,還有一個索引優先隊列minQueue存放最短路徑到各個頂點的有效最短邊。

  松弛:

  原路徑頂點0到頂點3放松到邊4-3,意味着需要判斷頂點0到頂點3的最短路徑是否從0-4,再從4-3,從如下圖來看若放松到4-3,則最小路徑變成了7.2+1.1=8.3>4.5,所以這時忽略放到4-3的請求。

   

  從如下圖來看若放松到4-3,則最小路徑變成了0.6+1.1=1.7<4.5,所以這時需要將4-3添加到edges[3]的最小路徑邊中,同時更改edgesWeight[3]=1.7

   

  如下通過一個簡單的過程來演示找到一副圖中的最短路徑,初始化狀態如下

             

  步驟一:遍歷起點0的鄰接邊,找到最短的邊0-1添加到最短路徑中,同時加入到edges數組中,並將0到1的權重之和添加到edgesWeight[]數組中,修改頂點0到各個頂點的有效橫切邊minQueue。

           

  步驟二:遍歷0和1組成的最短路徑的鄰接邊,通過比較0-2的距離0.35>0-1,1-2的距離0.19,找到最短路徑1-2加入到最短路徑中,並同時修改edges[]數組和edgesWeight數組

  修改頂點0和1組成的最短路徑到各個頂點的有效橫切邊minQueue。

 

         

  步驟三:找起點v到w的最短路徑時,只需從edges數組逆向查找即可,比如查找0-2的最短路徑,首先獲取edges[2],然后通過邊對象取到2的起點1,找到edge[1],依次找到起點0

  則可以發現最短路徑為0-1,1-2

五、 Disjstra代碼實現

import com.data.struct.common.list.queue.Queue;

import com.data.struct.common.tree.priority.queue.IndexMinPriorityQueue;

 

/**

 * Dijkstra算法實現

 * @author jiyukai

 */

public class DijkstraSP {

 

       // 存放當前頂點和上一個頂點的最短邊,索引為頂點,值為邊

       private DirectedEdge[] edges;

 

       // 存放起點s到其他頂點的權重之和

       private double[] edgesWeight;

 

       // 存放最小生成樹頂點與非最小生成樹頂點的目標橫切邊,索引為頂點

       private IndexMinPriorityQueue<Double> minQueue;

 

       public DijkstraSP(Digraph G, int s) throws Exception {

 

              edges = new DirectedEdge[G.V()];

              edgesWeight = new double[G.V()];

             

              // 初始化時將edgesWeight的值暫時設置為無窮大,起點處設置為0

              for (int i = 0; i < edgesWeight.length; i++) {

                     edgesWeight[i] = Double.POSITIVE_INFINITY;

              }

              edgesWeight[0] = 0.0;

 

              minQueue = new IndexMinPriorityQueue<>(G.V());

              minQueue.insert(0, 0.0);

 

              while (!minQueue.isEmpty()) {

                     // 松弛圖G中的頂點

                     relax(G, minQueue.delMin());

              }

 

       }

 

       /**

        * 進行松弛操作

        * @param g

        * @param delMin

        * @throws Exception

        */

       private void relax(Digraph G, int v) throws Exception {

              // 首先遍歷頂點的鄰接邊

              for (DirectedEdge e : G.qTable[v]) {

                     // 獲取邊的終點

                     int w = e.to();

 

                     // 起點到頂點w的權重是否大於起點到頂點v的權重+邊e的權重

                     // 如果大於,則修改s->w的路徑,edges[w]=e,並修改edgesWeight[v] =

                     // edgesWeight[v]+e.weight(),如果不大於,則忽略

                     if (edgesWeight[v] + e.weight() < edgesWeight[w]) {

 

                            edges[w] = e;

                            edgesWeight[w] = edgesWeight[w] + e.weight();

 

                            // 如果頂點w已經存在於優先隊列minQueue中,則重置頂點w的權重

                            if (minQueue.contains(w)) {

                                   minQueue.changeItem(w, edgesWeight[w]);

                            } else {

                                   // 如果頂點w沒有出現在優先隊列pq中,則把頂點w及其權重加入到pq中

                                   minQueue.insert(w, edgesWeight[w]);

                            }

                     }

              }

       }

 

       /**

        * 判斷從頂點s到頂點v是否可達

        * @param v

        * @return

        */

       public boolean hasPathTo(int v) {

              return edgesWeight[v] < Double.POSITIVE_INFINITY;

       }

 

       /**

        * 逆向查詢從起點s到頂點v的最短路徑中所有的邊

        * @param v

        * @return

        */

       public Queue<DirectedEdge> pathTo(int v){

              Queue<DirectedEdge> minPaths = new Queue<>();

 

              //不存在連通的路徑,則返回null

              if(!hasPathTo(v)) {

                     return null;

              }

             

              //逆向查找過程

              DirectedEdge e = null;

             

              while(true){

                     e = edges[v];

                     if(e==null) {

                            break;

                     }

                    

                     minPaths.enqueue(e);

                     v = e.from();

              }

             

              return minPaths;

       }

}


免責聲明!

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



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