一、背景
全文根據《算法-第四版》,Dijkstra(迪傑斯特拉)算法,一種單源最短路徑算法。我們把問題抽象為2步:1.數據結構抽象 2.實現。 分別對應第二章、第三章。
二、算法分析
2.1 數據結構
頂點+邊->圖。注意:Dijkstra算法的限定:
- 1.邊有權重,且非負
- 2.邊有向
2.1.1 加權有向邊
DirectedEdge,API抽象如下:
方法 | 描述 |
DirectedEdge(int v, int w, double weight) | 構造邊 |
double weight() | 邊的權重 |
int from() | 邊的起點 |
int to() | 邊的終點 |
2.1.2 加權有向圖
EdgeWeightedDigraph,API抽象如下:
方法 | 描述 |
EdgeWeightedDigraph(In in) | 從輸入流中構造圖 |
int V() | 頂點總數 |
int E() | 邊總數 |
void addEdge(DirectedEdge e) | 將邊e添加到圖中 |
Iterable<DirectedEdge> adj(int v) | 從頂點v指出的邊(鄰接表,一個哈希鏈表,key=頂點,value=頂點指出的邊鏈表) |
Iterable<DirectedEdge> edges() | 圖中全部邊 |
2.1.3 最短路徑
DijkstraSP, API抽象如下:
方法 | 描述 |
DijkstraSP(EdgeWeightedDigraph G, int s) | 構造最短路徑樹 |
double distTo(int v) | 頂點s->v的距離,初始化無窮大 |
boolean hasPathTo(int v) | 是否存在頂點s->v的路徑 |
Iterable<DirectedEdge> pathTo(int v) | s->v的路徑,不存在為null |
元素:
最短路徑樹中的邊(DirectedEdge[] edgeTo):
edgeTo[v]代表樹中連接v和父節點的邊(最短路徑最后一條邊數組),每個頂點都有一條這樣的邊,就組成了最短路徑樹。
原點到達頂點的距離:由頂點索引的數組 double[] distTo:
distTo[v] 代表原點到達頂點v的最短距離。
索引最小優先級隊列: IndexMinPQ<Double> pq:
int[] pq:索引二叉堆(元素=頂點v,對應keys[v]):數組從pq[0]代表原點其它頂點從pq[1]開始插入
Key[] keys:元素有序數組(按照pq值作為下標賦值)存儲到頂點的最短距離
2.2 算法核心
計算最短路徑,三步驟:
- 1.每次選取最小節頂點:如果選擇?使用最小堆排序,每次取堆頂元素即可。
- 2.遍歷從頂點的發出的全部邊
- 3.放松操作
三、具體實現
3.1 構造
3.1.1 元素迭代器
因為有遍歷需要,這里定義Bag<Item>類實現了Iterable<Item>迭代器接口,Item是元素。就是個簡單的某個元素的迭代器基本實現。
1 package study.algorithm.base; 2 3 import java.util.Iterator; 4 import java.util.NoSuchElementException; 5 6 /** 7 * The {@code Bag} class represents a bag (or multiset) of 8 * generic items. It supports insertion and iterating over the 9 * items in arbitrary order. 10 * <p> 11 * This implementation uses a singly linked list with a static nested class Node. 12 * See {@link LinkedBag} for the version from the 13 * textbook that uses a non-static nested class. 14 * See {@link ResizingArrayBag} for a version that uses a resizing array. 15 * The <em>add</em>, <em>isEmpty</em>, and <em>size</em> operations 16 * take constant time. Iteration takes time proportional to the number of items. 17 * <p> 18 * For additional documentation, see <a href="https://algs4.cs.princeton.edu/13stacks">Section 1.3</a> of 19 * <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne. 20 * 21 * @author Robert Sedgewick 22 * @author Kevin Wayne 23 * 24 * @param <Item> the generic type of an item in this bag 25 */ 26 public class Bag<Item> implements Iterable<Item> { 27 /** 28 * 首節點 29 */ 30 private Node<Item> first; 31 /** 32 * 元素個數 33 */ 34 private int n; 35 36 /** 37 * 鏈接表 38 * @param <Item> 39 */ 40 private static class Node<Item> { 41 private Item item; 42 private Node<Item> next; 43 } 44 45 /** 46 * 初始化一個空包 47 */ 48 public Bag() { 49 first = null; 50 n = 0; 51 } 52 53 /** 54 * Returns true if this bag is empty. 55 * 56 * @return {@code true} if this bag is empty; 57 * {@code false} otherwise 58 */ 59 public boolean isEmpty() { 60 return first == null; 61 } 62 63 /** 64 * Returns the number of items in this bag. 65 * 66 * @return the number of items in this bag 67 */ 68 public int size() { 69 return n; 70 } 71 72 /** 73 * Adds the item to this bag. 74 * 75 * @param item the item to add to this bag 76 */ 77 public void add(Item item) { 78 // 保留老的首節點 79 Node<Item> oldfirst = first; 80 // 構造一個新首節點 81 first = new Node<Item>(); 82 // item為新首節點item 83 first.item = item; 84 // 新節點的next節點指向老的首節點 85 first.next = oldfirst; 86 n++; 87 } 88 89 90 /** 91 * Returns an iterator that iterates over the items in this bag in arbitrary order. 92 * 93 * @return an iterator that iterates over the items in this bag in arbitrary order 94 */ 95 @Override 96 public Iterator<Item> iterator() { 97 return new LinkedIterator(first); 98 } 99 100 /** 101 * 鏈接迭代器,不支持移除 102 */ 103 private class LinkedIterator implements Iterator<Item> { 104 private Node<Item> current; 105 106 public LinkedIterator(Node<Item> first) { 107 current = first; 108 } 109 110 @Override 111 public boolean hasNext() { return current != null; } 112 @Override 113 public void remove() { throw new UnsupportedOperationException(); } 114 115 @Override 116 public Item next() { 117 if (!hasNext()) { 118 throw new NoSuchElementException(); 119 } 120 Item item = current.item; 121 // 下一節點 122 current = current.next; 123 return item; 124 } 125 } 126 127 /** 128 * Unit tests the {@code Bag} data type. 129 * 130 * @param args the command-line arguments 131 */ 132 public static void main(String[] args) { 133 Bag<String> bag = new Bag<String>(); 134 while (!StdIn.isEmpty()) { 135 String item = StdIn.readString(); 136 bag.add(item); 137 } 138 139 StdOut.println("size of bag = " + bag.size()); 140 for (String s : bag) { 141 StdOut.println(s); 142 } 143 } 144 145 }
3.1.2 具體構造
1. 從輸入流中初始化圖,輸入流格式(括號內為注釋,實際文件中不存在):
8(頂點數)
15(邊數)
4 5 0.35(邊4->5 權重=0.35)
5 4 0.35
4 7 0.37
5 7 0.28
7 5 0.28
5 1 0.32
0 4 0.38
0 2 0.26
7 3 0.39
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93
如下圖中public EdgeWeightedDigraph(In in)構造方法,核心:
往鄰接表(頂點作為數組下標)中添加帶權重邊。
1 package study.algorithm.graph; 2 3 import study.algorithm.base.*; 4 5 import java.util.NoSuchElementException; 6 7 /*** 8 * @Description 邊權重有向圖 9 * @author denny.zhang 10 * @date 2020/4/24 9:58 上午 11 */ 12 public class EdgeWeightedDigraph { 13 private static final String NEWLINE = System.getProperty("line.separator"); 14 15 /** 16 * 頂點總數 17 */ 18 private final int V; 19 /** 20 * 邊總數 21 */ 22 private int E; 23 /** 24 * 鄰接表(每個元素Bag代表 由某個頂點為起點的邊數組,按頂點順序排列),adjacency list 25 */ 26 private Bag<DirectedEdge>[] adj; 27 28 /** 29 * 從輸入流中初始化圖,輸入流格式: 30 * 8(頂點數) 31 * 15(邊總數) 32 * 4 5 0.35(每一條邊 4->5 權重0.35) 33 * 5 4 0.35 34 * 4 7 0.37 35 * ... 36 * 37 * @param in the input stream 38 * @throws IllegalArgumentException if {@code in} is {@code null} 39 * @throws IllegalArgumentException if the endpoints of any edge are not in prescribed range 40 * @throws IllegalArgumentException if the number of vertices or edges is negative 41 */ 42 public EdgeWeightedDigraph(In in) { 43 if (in == null) { 44 throw new IllegalArgumentException("argument is null"); 45 } 46 try { 47 // 1.讀取頂點數 48 this.V = in.readInt(); 52 // 初始化鄰接表 53 adj = (Bag<DirectedEdge>[]) new Bag[V]; 54 for (int v = 0; v < V; v++) { 55 adj[v] = new Bag<DirectedEdge>(); 56 } 57 // 2.讀取邊數 58 int E = in.readInt(); 59 62 for (int i = 0; i < E; i++) { 63 int v = in.readInt(); 64 int w = in.readInt(); 67 // 3.讀取邊的權重 68 double weight = in.readDouble(); 69 // 添加權重邊 70 addEdge(new DirectedEdge(v, w, weight)); 71 } 72 } 73 catch (NoSuchElementException e) { 74 throw new IllegalArgumentException("invalid input format in EdgeWeightedDigraph constructor", e); 75 } 76 } 77 78 /** 79 * 頂點數 80 * 81 * @return the number of vertices in this edge-weighted digraph 82 */ 83 public int V() { 84 return V; 85 } 86 87 /** 88 * 邊數 89 * 90 * @return the number of edges in this edge-weighted digraph 91 */ 92 public int E() { 93 return E; 94 } 95 106 /** 107 * 往圖中添加邊 108 * 109 * @param e the edge 110 * @throws IllegalArgumentException unless endpoints of edge are between {@code 0} 111 * and {@code V-1} 112 */ 113 public void addEdge(DirectedEdge e) { 114 // 邊的起點 115 int v = e.from(); 116 // 邊的終點 117 int w = e.to();120 // 起點v的鄰接表,加入一條邊 121 adj[v].add(e); 122 // 邊總數+1 123 E++; 124 } 125 126 127 /** 128 * 返回從頂點V 指出的全部可迭代邊(鄰接表) 129 * 130 * @param v the vertex 131 * @return the directed edges incident from vertex {@code v} as an Iterable 132 * @throws IllegalArgumentException unless {@code 0 <= v < V} 133 */ 134 public Iterable<DirectedEdge> adj(int v) { 135 validateVertex(v); 136 return adj[v]; 137 } 138 139 /** 140 * 返回全部有向邊 141 * 142 * @return all edges in this edge-weighted digraph, as an iterable 143 */ 144 public Iterable<DirectedEdge> edges() { 145 Bag<DirectedEdge> list = new Bag<DirectedEdge>(); 146 // 遍歷全部頂點 147 for (int v = 0; v < V; v++) { 148 // 每個頂點的鄰接表(指出邊) 149 for (DirectedEdge e : adj(v)) { 150 // 指出邊入list 151 list.add(e); 152 } 153 } 154 return list; 155 } 156 157 /** 158 * Returns a string representation of this edge-weighted digraph. 159 * 160 * @return the number of vertices <em>V</em>, followed by the number of edges <em>E</em>, 161 * followed by the <em>V</em> adjacency lists of edges 162 */ 163 @Override 164 public String toString() { 165 StringBuilder s = new StringBuilder(); 166 s.append(V + " " + E + NEWLINE); 167 for (int v = 0; v < V; v++) { 168 s.append(v + ": "); 169 for (DirectedEdge e : adj[v]) { 170 s.append(e + " "); 171 } 172 s.append(NEWLINE); 173 } 174 return s.toString(); 175 } 176 177 /** 178 * Unit tests the {@code EdgeWeightedDigraph} data type. 179 * 180 * @param args the command-line arguments 181 */ 182 public static void main(String[] args) { 183 In in = new In(args[0]); 184 EdgeWeightedDigraph G = new EdgeWeightedDigraph(in); 185 StdOut.println(G); 186 } 187 188 }
3.2 計算最短路徑
3.2.1 索引優先隊列
1 package study.algorithm.base; 2 3 import java.util.Iterator; 4 import java.util.NoSuchElementException; 5 6 /** 7 * 索引最小優先級隊列 8 * 9 * @param <Key> 10 */ 11 public class IndexMinPQ<Key extends Comparable<Key>> implements Iterable<Integer> { 12 /** 13 * 元素數量上限 14 */ 15 private int maxN; 16 /** 17 * 元素數量 18 */ 19 private int n; 20 /** 21 * 索引二叉堆(元素=頂點v,對應keys[v]):pq[0]代表原點,其它頂點從pq[1]開始插入 22 */ 23 private int[] pq; 24 /** 25 * 標記索引為i的元素在二叉堆中的位置。pq的反轉數組(qp[index]=i):qp[pq[i]] = pq[qp[i]] = i 26 */ 27 private int[] qp; 28 29 /** 30 * 元素有序數組(按照pq的索引賦值) 31 */ 32 private Key[] keys; 33 34 /** 35 * 初始化一個空索引優先隊列,索引范圍:0 ~ maxN-1 36 * 37 * @param maxN the keys on this priority queue are index from {@code 0} 38 * {@code maxN - 1} 39 * @throws IllegalArgumentException if {@code maxN < 0} 40 */ 41 public IndexMinPQ(int maxN) { 42 if (maxN < 0) throw new IllegalArgumentException(); 43 this.maxN = maxN; 44 // 初始有0個元素 45 n = 0; 46 // 初始化鍵數組長度為maxN + 1 47 keys = (Key[]) new Comparable[maxN + 1]; 48 // 初始化"鍵值對"數組長度為maxN + 1 49 pq = new int[maxN + 1]; 50 // 初始化"值鍵對"數組長度為maxN + 1 51 qp = new int[maxN + 1]; 52 // 遍歷給"值鍵對"數組賦值-1,后續只要!=-1,即包含i 53 for (int i = 0; i <= maxN; i++) 54 qp[i] = -1; 55 } 56 57 /** 58 * Returns true if this priority queue is empty. 59 * 60 * @return {@code true} if this priority queue is empty; 61 * {@code false} otherwise 62 */ 63 public boolean isEmpty() { 64 return n == 0; 65 } 66 67 /** 68 * Is {@code i} an index on this priority queue? 69 * 70 * @param i an index 71 * @return {@code true} if {@code i} is an index on this priority queue; 72 * {@code false} otherwise 73 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 74 */ 75 public boolean contains(int i) { 76 validateIndex(i); 77 return qp[i] != -1; 78 } 79 80 /** 81 * Returns the number of keys on this priority queue. 82 * 83 * @return the number of keys on this priority queue 84 */ 85 public int size() { 86 return n; 87 } 88 89 /** 90 * 插入一個元素,將元素key關聯索引i 91 * 92 * @param i an index 93 * @param key the key to associate with index {@code i} 94 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 95 * @throws IllegalArgumentException if there already is an item associated 96 * with index {@code i} 97 */ 98 public void insert(int i, Key key) { 99 validateIndex(i); 100 if (contains(i)) throw new IllegalArgumentException("index is already in the priority queue"); 101 // 元素個數+1 102 n++; 103 // 索引為i的二叉堆位置為n 104 qp[i] = n; 105 // 二叉堆底部插入新元素,值=i 106 pq[n] = i; 107 // 索引i對應的元素賦值 108 keys[i] = key; 109 // 二叉堆中,上浮最后一個元素(小值上浮) 110 swim(n); 111 } 112 113 /** 114 * 返回最小元素的索引 115 * 116 * @return an index associated with a minimum key 117 * @throws NoSuchElementException if this priority queue is empty 118 */ 119 public int minIndex() { 120 if (n == 0) throw new NoSuchElementException("Priority queue underflow"); 121 return pq[1]; 122 } 123 124 /** 125 * 返回最小元素(key) 126 * 127 * @return a minimum key 128 * @throws NoSuchElementException if this priority queue is empty 129 */ 130 public Key minKey() { 131 if (n == 0) throw new NoSuchElementException("Priority queue underflow"); 132 return keys[pq[1]]; 133 } 134 135 /** 136 * 刪除最小值key,並返回最小值 137 * 138 * @return an index associated with a minimum key 139 * @throws NoSuchElementException if this priority queue is empty 140 */ 141 public int delMin() { 142 if (n == 0) throw new NoSuchElementException("Priority queue underflow"); 143 // pq[1]即為索引最小值 144 int min = pq[1]; 145 // 交換第一個元素和最后一個元素 146 exch(1, n--); 147 // 把新換來的第一個元素下沉 148 sink(1); 149 // 校驗下沉后,最后一個元素是最小值 150 assert min == pq[n+1]; 151 // 恢復初始值,-1即代表該元素已刪除 152 qp[min] = -1; // delete 153 // 方便垃圾回收 154 keys[min] = null; 155 // 最后一個元素(索引)賦值-1 156 pq[n+1] = -1; // not needed 157 return min; 158 } 159 160 /** 161 * Returns the key associated with index {@code i}. 162 * 163 * @param i the index of the key to return 164 * @return the key associated with index {@code i} 165 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 166 * @throws NoSuchElementException no key is associated with index {@code i} 167 */ 168 public Key keyOf(int i) { 169 validateIndex(i); 170 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue"); 171 else return keys[i]; 172 } 173 174 /** 175 * Change the key associated with index {@code i} to the specified value. 176 * 177 * @param i the index of the key to change 178 * @param key change the key associated with index {@code i} to this key 179 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 180 * @throws NoSuchElementException no key is associated with index {@code i} 181 */ 182 public void changeKey(int i, Key key) { 183 validateIndex(i); 184 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue"); 185 keys[i] = key; 186 swim(qp[i]); 187 sink(qp[i]); 188 } 189 190 /** 191 * Change the key associated with index {@code i} to the specified value. 192 * 193 * @param i the index of the key to change 194 * @param key change the key associated with index {@code i} to this key 195 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 196 * @deprecated Replaced by {@code changeKey(int, Key)}. 197 */ 198 @Deprecated 199 public void change(int i, Key key) { 200 changeKey(i, key); 201 } 202 203 /** 204 * 減小索引i對應的值為key 205 * 更新: 206 * 1.元素數組keys[] 207 * 2.小頂二叉堆pq[] 208 * 209 * @param i the index of the key to decrease 210 * @param key decrease the key associated with index {@code i} to this key 211 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 212 * @throws IllegalArgumentException if {@code key >= keyOf(i)} 213 * @throws NoSuchElementException no key is associated with index {@code i} 214 */ 215 public void decreaseKey(int i, Key key) { 216 validateIndex(i); 217 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue"); 218 // key 值一樣,報錯 219 if (keys[i].compareTo(key) == 0) 220 throw new IllegalArgumentException("Calling decreaseKey() with a key equal to the key in the priority queue"); 221 // key比當前值大,報錯 222 if (keys[i].compareTo(key) < 0) 223 throw new IllegalArgumentException("Calling decreaseKey() with a key strictly greater than the key in the priority queue"); 224 // key比當前值小,把key賦值進去 225 keys[i] = key; 226 // 小值上浮(qp[i]=索引i在二叉堆pq[]中的位置) 227 swim(qp[i]); 228 } 229 230 /** 231 * Increase the key associated with index {@code i} to the specified value. 232 * 233 * @param i the index of the key to increase 234 * @param key increase the key associated with index {@code i} to this key 235 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 236 * @throws IllegalArgumentException if {@code key <= keyOf(i)} 237 * @throws NoSuchElementException no key is associated with index {@code i} 238 */ 239 public void increaseKey(int i, Key key) { 240 validateIndex(i); 241 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue"); 242 if (keys[i].compareTo(key) == 0) 243 throw new IllegalArgumentException("Calling increaseKey() with a key equal to the key in the priority queue"); 244 if (keys[i].compareTo(key) > 0) 245 throw new IllegalArgumentException("Calling increaseKey() with a key strictly less than the key in the priority queue"); 246 keys[i] = key; 247 sink(qp[i]); 248 } 249 250 /** 251 * Remove the key associated with index {@code i}. 252 * 253 * @param i the index of the key to remove 254 * @throws IllegalArgumentException unless {@code 0 <= i < maxN} 255 * @throws NoSuchElementException no key is associated with index {@code i} 256 */ 257 public void delete(int i) { 258 validateIndex(i); 259 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue"); 260 int index = qp[i]; 261 exch(index, n--); 262 swim(index); 263 sink(index); 264 keys[i] = null; 265 qp[i] = -1; 266 } 267 268 // throw an IllegalArgumentException if i is an invalid index 269 private void validateIndex(int i) { 270 if (i < 0) throw new IllegalArgumentException("index is negative: " + i); 271 if (i >= maxN) throw new IllegalArgumentException("index >= capacity: " + i); 272 } 273 274 /*************************************************************************** 275 * General helper functions. 276 ***************************************************************************/ 277 private boolean greater(int i, int j) { 278 return keys[pq[i]].compareTo(keys[pq[j]]) > 0; 279 } 280 281 private void exch(int i, int j) { 282 int swap = pq[i]; 283 pq[i] = pq[j]; 284 pq[j] = swap; 285 qp[pq[i]] = i; 286 qp[pq[j]] = j; 287 } 288 289 290 /*************************************************************************** 291 * Heap helper functions. 292 ***************************************************************************/ 293 private void swim(int k) { 294 // 如果父節點值比當前節點值大,交換,父節點作為當前節點,輪詢。即小值上浮。 295 while (k > 1 && greater(k/2, k)) { 296 exch(k, k/2); 297 k = k/2; 298 } 299 } 300 301 private void sink(int k) { 302 while (2*k <= n) { 303 int j = 2*k; 304 if (j < n && greater(j, j+1)) j++; 305 if (!greater(k, j)) break; 306 exch(k, j); 307 k = j; 308 } 309 } 310 311 312 /*************************************************************************** 313 * Iterators. 314 ***************************************************************************/ 315 316 /** 317 * Returns an iterator that iterates over the keys on the 318 * priority queue in ascending order. 319 * The iterator doesn't implement {@code remove()} since it's optional. 320 * 321 * @return an iterator that iterates over the keys in ascending order 322 */ 323 @Override 324 public Iterator<Integer> iterator() { return new HeapIterator(); } 325 326 private class HeapIterator implements Iterator<Integer> { 327 // create a new pq 328 private IndexMinPQ<Key> copy; 329 330 // add all elements to copy of heap 331 // takes linear time since already in heap order so no keys move 332 public HeapIterator() { 333 copy = new IndexMinPQ<Key>(pq.length - 1); 334 for (int i = 1; i <= n; i++) 335 copy.insert(pq[i], keys[pq[i]]); 336 } 337 338 @Override 339 public boolean hasNext() { return !copy.isEmpty(); } 340 @Override 341 public void remove() { throw new UnsupportedOperationException(); } 342 343 @Override 344 public Integer next() { 345 if (!hasNext()) throw new NoSuchElementException(); 346 return copy.delMin(); 347 } 348 } 349 350 351 /** 352 * Unit tests the {@code IndexMinPQ} data type. 353 * 354 * @param args the command-line arguments 355 */ 356 public static void main(String[] args) { 357 // insert a bunch of strings 358 String[] strings = { "it", "was", "the", "best", "of", "times", "it", "was", "the", "worst" }; 359 360 IndexMinPQ<String> pq = new IndexMinPQ<String>(strings.length); 361 for (int i = 0; i < strings.length; i++) { 362 pq.insert(i, strings[i]); 363 } 364 365 // delete and print each key 366 while (!pq.isEmpty()) { 367 int i = pq.delMin(); 368 StdOut.println(i + " " + strings[i]); 369 } 370 StdOut.println(); 371 372 // reinsert the same strings 373 for (int i = 0; i < strings.length; i++) { 374 pq.insert(i, strings[i]); 375 } 376 377 // print each key using the iterator 378 for (int i : pq) { 379 StdOut.println(i + " " + strings[i]); 380 } 381 while (!pq.isEmpty()) { 382 pq.delMin(); 383 } 384 385 } 386 }
3.2.2 最短路徑
1 package study.algorithm.graph; 2 3 import study.algorithm.base.In; 4 import study.algorithm.base.IndexMinPQ; 5 import study.algorithm.base.Stack; 6 import study.algorithm.base.StdOut; 7 8 /*** 9 * @Description 邊權重非負的加權有向圖的單起點最短路徑樹 10 * @author denny.zhang 11 * @date 2020/4/23 11:29 上午 12 */ 13 public class DijkstraSP { 14 15 /** 16 * 最短路徑數組,元素:到所有頂點的最短路徑 17 */ 18 private double[] distTo; 19 20 /** 21 * 有向邊數組:最短路徑最后一條邊數組 22 */ 23 private DirectedEdge[] edgeTo; 24 25 /** 26 * 頂點作為下標,索引最小優先級隊列 27 */ 28 private IndexMinPQ<Double> pq; 29 30 /** 31 * 計算從原點S 到 其它所有頂點 的"最短路徑" 邊權重 圖 32 * 33 * @param G the edge-weighted digraph 邊權重圖 34 * @param s the source vertex 原點 35 * @throws IllegalArgumentException if an edge weight is negative 36 * @throws IllegalArgumentException unless {@code 0 <= s < V} 37 */ 38 public DijkstraSP(EdgeWeightedDigraph G, int s) { 39 // 負權重校驗 40 for (DirectedEdge e : G.edges()) { 41 if (e.weight() < 0) { 42 throw new IllegalArgumentException("edge " + e + " has negative weight"); 43 } 44 } 45 // 最短路徑數組長度=頂點個數 46 distTo = new double[G.V()]; 47 // 構造長度為頂點總數的最短路徑邊數組 48 edgeTo = new DirectedEdge[G.V()]; 49 // 校驗原點值 50 validateVertex(s); 51 // 初始化所有頂點的路徑為無窮大 52 for (int v = 0; v < G.V(); v++) { 53 distTo[v] = Double.POSITIVE_INFINITY; 54 } 55 // 初始化到原點最小路徑為0 56 distTo[s] = 0.0; 57 58 // 構造一個長度為 頂點總數的 索引最小優先隊列 59 pq = new IndexMinPQ<Double>(G.V()); 60 // 把原點插入,路徑為0 61 pq.insert(s, distTo[s]); 62 // 只要隊列不空(從上往下,順序遍歷一遍pq[]), 63 while (!pq.isEmpty()) { 64 // 刪除最小key(即pq[1]),並返回最小值(頂點) 65 int v = pq.delMin(); 66 // 遍歷頂點v的鄰接表,每一條邊 67 for (DirectedEdge e : G.adj(v)) { 68 // 放松邊 69 relax(e); 70 } 71 } 72 73 // 校驗 74 assert check(G, s); 75 } 76 77 /** 78 * 放松並更新pq 79 * @param e 80 */ 81 private void relax(DirectedEdge e) { 82 // 起點、終點 83 int v = e.from(), w = e.to(); 84 // 如果原點到終點w的距離 > 原點到起點v的距離+邊權重 說明原點到w松弛了 85 if (distTo[w] > distTo[v] + e.weight()) { 86 // 最新距離 87 distTo[w] = distTo[v] + e.weight(); 88 // 到終點w的邊賦值為新邊 89 edgeTo[w] = e; 90 // 如果優先隊列已經包含終點w 91 if (pq.contains(w)) { 92 // 比較下標為w的key如果>當前路徑(即當前值比隊列中值小),重新排序 93 pq.decreaseKey(w, distTo[w]); 94 } else { 95 // 不包含,插入並排序 96 pq.insert(w, distTo[w]); 97 } 98 } 99 } 100 101 /** 102 * s->v的最短路徑 103 * @param v the destination vertex 104 * @return the length of a shortest path from the source vertex {@code s} to vertex {@code v}; 105 * {@code Double.POSITIVE_INFINITY} if no such path 106 * @throws IllegalArgumentException unless {@code 0 <= v < V} 107 */ 108 public double distTo(int v) { 109 validateVertex(v); 110 return distTo[v]; 111 } 112 113 /** 114 * s->v是否可達 115 * 116 * @param v the destination vertex 117 * @return {@code true} if there is a path from the source vertex 118 * {@code s} to vertex {@code v}; {@code false} otherwise 119 * @throws IllegalArgumentException unless {@code 0 <= v < V} 120 */ 121 public boolean hasPathTo(int v) { 122 validateVertex(v); 123 return distTo[v] < Double.POSITIVE_INFINITY; 124 } 125 126 /** 127 * s->v的最短可迭代邊(1->2->3) 128 * 129 * @param v the destination vertex 130 * @return a shortest path from the source vertex {@code s} to vertex {@code v} 131 * as an iterable of edges, and {@code null} if no such path 132 * @throws IllegalArgumentException unless {@code 0 <= v < V} 133 */ 134 public Iterable<DirectedEdge> pathTo(int v) { 135 validateVertex(v); 136 if (!hasPathTo(v)) { 137 return null; 138 } 139 // 可迭代有向邊棧 140 Stack<DirectedEdge> path = new Stack<DirectedEdge>(); 141 // e是頂點v的最短路徑樹的最后一條邊,沿着邊往上追溯上一個頂點 3->2->1 142 for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) { 143 // 壓棧 144 path.push(e); 145 } 146 return path; 147 } 148 149 150 // check optimality conditions: 151 // (i) for all edges e: distTo[e.to()] <= distTo[e.from()] + e.weight() 152 // (ii) for all edge e on the SPT: distTo[e.to()] == distTo[e.from()] + e.weight() 153 private boolean check(EdgeWeightedDigraph G, int s) { 154 155 // 校驗邊權重不為負值 156 for (DirectedEdge e : G.edges()) { 157 if (e.weight() < 0) { 158 System.err.println("negative edge weight detected"); 159 return false; 160 } 161 } 162 163 // 校驗到頂點的路徑為0且到頂點的邊為空 164 if (distTo[s] != 0.0 || edgeTo[s] != null) { 165 System.err.println("distTo[s] and edgeTo[s] inconsistent"); 166 return false; 167 } 168 // 遍歷頂點 169 for (int v = 0; v < G.V(); v++) { 170 // 起點跳過 171 if (v == s) { 172 continue; 173 } 174 // 到頂點v的最后一條邊為空(不可達) 且 到頂點v的最短路徑不是無窮大(即有值)兩者沖突 175 if (edgeTo[v] == null && distTo[v] != Double.POSITIVE_INFINITY) { 176 System.err.println("distTo[] and edgeTo[] inconsistent"); 177 return false; 178 } 179 } 180 181 // 校驗所有邊非松弛 182 for (int v = 0; v < G.V(); v++) { 183 // 遍歷頂點v的鄰接邊 184 for (DirectedEdge e : G.adj(v)) { 185 int w = e.to(); 186 // 校驗松弛 187 if (distTo[v] + e.weight() < distTo[w]) { 188 System.err.println("edge " + e + " not relaxed"); 189 return false; 190 } 191 } 192 } 193 194 // 校驗最短路徑樹:滿足 distTo[w] == distTo[v] + e.weight() 195 for (int w = 0; w < G.V(); w++) { 196 // 跳過不可達頂點 197 if (edgeTo[w] == null) { 198 continue; 199 } 200 // 最后一條邊 201 DirectedEdge e = edgeTo[w]; 202 // 起點 203 int v = e.from(); 204 //終點 205 if (w != e.to()) { 206 return false; 207 } 208 // 校驗:最短路勁樹,起點路徑+權重=終點路徑 209 if (distTo[v] + e.weight() != distTo[w]) { 210 System.err.println("edge " + e + " on shortest path not tight"); 211 return false; 212 } 213 } 214 return true; 215 } 216 217 // throw an IllegalArgumentException unless {@code 0 <= v < V} 218 private void validateVertex(int v) { 219 int V = distTo.length; 220 if (v < 0 || v >= V) { 221 throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1)); 222 } 223 } 224 225 /** 226 * Unit tests the {@code DijkstraSP} data type. 227 * 228 * @param args the command-line arguments 229 */ 230 public static void main(String[] args) { 231 // 圖文件名稱 232 In in = new In(args[0]); 233 // 構造邊權重有向圖 234 EdgeWeightedDigraph G = new EdgeWeightedDigraph(in); 235 // 頂點 236 int s = Integer.parseInt(args[1]); 237 238 // 計算最短路徑 239 DijkstraSP sp = new DijkstraSP(G, s); 240 241 // 遍歷所有頂點 242 for (int t = 0; t < G.V(); t++) { 243 // 可達 244 if (sp.hasPathTo(t)) { 245 // 原點到t的路徑 長度 246 StdOut.printf("%d to %d (%.2f) ", s, t, sp.distTo(t)); 247 // 原點到t的路徑圖 248 for (DirectedEdge e : sp.pathTo(t)) { 249 StdOut.print(e + " "); 250 } 251 // 換行 252 StdOut.println(); 253 } 254 // 不可達 255 else { 256 StdOut.printf("%d to %d no path\n", s, t); 257 } 258 } 259 } 260 261 }
四、測試結果
4.1 測試准備
本地生存一個文件 tinyEWD.txt,內容如下:
8 15 4 5 0.35 5 4 0.35 4 7 0.37 5 7 0.28 7 5 0.28 5 1 0.32 0 4 0.38 0 2 0.26 7 3 0.39 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93
4.2 測試
本地運行DijkstraSP,配置運行參數,以idea為例:第一個入參是文件地址,第二個參數代表原點是0,計算從原點(頂點0)到 其它所有頂點 的"最短路徑" 邊權重 圖:
運行的最短路徑,結果如下: