一、 加權有向圖概述
加權有向圖是在加權無向圖的基礎上,給邊添加了方向,並且一條加權有向邊只會在一個頂點的鄰接表中出現。
二、 加權有向圖實現
為了體現邊的有向性,我們需要知道邊的起點和終點,參照如下來構建有向邊,而有向圖的構建只需在前面無向圖的基礎上,將無向邊對象更換為有向邊對象即可
/** * 有向邊對象 * @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; } } |