最短路径树:Dijstra算法
一、背景
全文根据《算法-第四版》,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是元素。就是个简单的某个元素的迭代器基本实现。
package study.algorithm.base; import java.util.Iterator;
import java.util.NoSuchElementException; /**
* The {@code Bag} class represents a bag (or multiset) of
* generic items. It supports insertion and iterating over the
* items in arbitrary order.
* <p>
* This implementation uses a singly linked list with a static nested class Node.
* See {@link LinkedBag} for the version from the
* textbook that uses a non-static nested class.
* See {@link ResizingArrayBag} for a version that uses a resizing array.
* The <em>add</em>, <em>isEmpty</em>, and <em>size</em> operations
* take constant time. Iteration takes time proportional to the number of items.
* <p>
* For additional documentation, see <a href="https://algs4.cs.princeton.edu/13stacks">Section 1.3</a> of
* <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*
* @param <Item> the generic type of an item in this bag
*/
public class Bag<Item> implements Iterable<Item> {
/**
* 首节点
*/
private Node<Item> first;
/**
* 元素个数
*/
private int n; /**
* 链接表
* @param <Item>
*/
private static class Node<Item> {
private Item item;
private Node<Item> next;
} /**
* 初始化一个空包
*/
public Bag() {
first = null;
n = 0;
} /**
* Returns true if this bag is empty.
*
* @return {@code true} if this bag is empty;
* {@code false} otherwise
*/
public boolean isEmpty() {
return first == null;
} /**
* Returns the number of items in this bag.
*
* @return the number of items in this bag
*/
public int size() {
return n;
} /**
* Adds the item to this bag.
*
* @param item the item to add to this bag
*/
public void add(Item item) {
// 保留老的首节点
Node<Item> oldfirst = first;
// 构造一个新首节点
first = new Node<Item>();
// item为新首节点item
first.item = item;
// 新节点的next节点指向老的首节点
first.next = oldfirst;
n++;
} /**
* Returns an iterator that iterates over the items in this bag in arbitrary order.
*
* @return an iterator that iterates over the items in this bag in arbitrary order
*/
@Override
public Iterator<Item> iterator() {
return new LinkedIterator(first);
} /**
* 链接迭代器,不支持移除
*/
private class LinkedIterator implements Iterator<Item> {
private Node<Item> current; public LinkedIterator(Node<Item> first) {
current = first;
} @Override
public boolean hasNext() { return current != null; }
@Override
public void remove() { throw new UnsupportedOperationException(); } @Override
public Item next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Item item = current.item;
// 下一节点
current = current.next;
return item;
}
} /**
* Unit tests the {@code Bag} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
Bag<String> bag = new Bag<String>();
while (!StdIn.isEmpty()) {
String item = StdIn.readString();
bag.add(item);
} StdOut.println("size of bag = " + bag.size());
for (String s : bag) {
StdOut.println(s);
}
} }
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)构造方法,核心:
往邻接表(顶点作为数组下标)中添加带权重边。
package study.algorithm.graph; import study.algorithm.base.*; import java.util.NoSuchElementException; /***
* @Description 边权重有向图
* @author denny.zhang
* @date 2020/4/24 9:58 上午
*/
public class EdgeWeightedDigraph {
private static final String NEWLINE = System.getProperty("line.separator"); /**
* 顶点总数
*/
private final int V;
/**
* 边总数
*/
private int E;
/**
* 邻接表(每个元素Bag代表 由某个顶点为起点的边数组,按顶点顺序排列),adjacency list
*/
private Bag<DirectedEdge>[] adj; /**
* 从输入流中初始化图,输入流格式:
* 8(顶点数)
* 15(边总数)
* 4 5 0.35(每一条边 4->5 权重0.35)
* 5 4 0.35
* 4 7 0.37
* ...
*
* @param in the input stream
* @throws IllegalArgumentException if {@code in} is {@code null}
* @throws IllegalArgumentException if the endpoints of any edge are not in prescribed range
* @throws IllegalArgumentException if the number of vertices or edges is negative
*/
public EdgeWeightedDigraph(In in) {
if (in == null) {
throw new IllegalArgumentException("argument is null");
}
try {
// 1.读取顶点数
this.V = in.readInt(); // 初始化邻接表
adj = (Bag<DirectedEdge>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<DirectedEdge>();
}
// 2.读取边数
int E = in.readInt();
for (int i = 0; i < E; i++) {
int v = in.readInt();
int w = in.readInt();
// 3.读取边的权重
double weight = in.readDouble();
// 添加权重边
addEdge(new DirectedEdge(v, w, weight));
}
}
catch (NoSuchElementException e) {
throw new IllegalArgumentException("invalid input format in EdgeWeightedDigraph constructor", e);
}
} /**
* 顶点数
*
* @return the number of vertices in this edge-weighted digraph
*/
public int V() {
return V;
} /**
* 边数
*
* @return the number of edges in this edge-weighted digraph
*/
public int E() {
return E;
} /**
* 往图中添加边
*
* @param e the edge
* @throws IllegalArgumentException unless endpoints of edge are between {@code 0}
* and {@code V-1}
*/
public void addEdge(DirectedEdge e) {
// 边的起点
int v = e.from();
// 边的终点
int w = e.to(); // 起点v的邻接表,加入一条边
adj[v].add(e);
// 边总数+1
E++;
} /**
* 返回从顶点V 指出的全部可迭代边(邻接表)
*
* @param v the vertex
* @return the directed edges incident from vertex {@code v} as an Iterable
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public Iterable<DirectedEdge> adj(int v) {
validateVertex(v);
return adj[v];
} /**
* 返回全部有向边
*
* @return all edges in this edge-weighted digraph, as an iterable
*/
public Iterable<DirectedEdge> edges() {
Bag<DirectedEdge> list = new Bag<DirectedEdge>();
// 遍历全部顶点
for (int v = 0; v < V; v++) {
// 每个顶点的邻接表(指出边)
for (DirectedEdge e : adj(v)) {
// 指出边入list
list.add(e);
}
}
return list;
} /**
* Returns a string representation of this edge-weighted digraph.
*
* @return the number of vertices <em>V</em>, followed by the number of edges <em>E</em>,
* followed by the <em>V</em> adjacency lists of edges
*/
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append(V + " " + E + NEWLINE);
for (int v = 0; v < V; v++) {
s.append(v + ": ");
for (DirectedEdge e : adj[v]) {
s.append(e + " ");
}
s.append(NEWLINE);
}
return s.toString();
} /**
* Unit tests the {@code EdgeWeightedDigraph} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
In in = new In(args[0]);
EdgeWeightedDigraph G = new EdgeWeightedDigraph(in);
StdOut.println(G);
} }
3.2 计算最短路径
3.2.1 索引优先队列
package study.algorithm.base; import java.util.Iterator;
import java.util.NoSuchElementException; /**
* 索引最小优先级队列
*
* @param <Key>
*/
public class IndexMinPQ<Key extends Comparable<Key>> implements Iterable<Integer> {
/**
* 元素数量上限
*/
private int maxN;
/**
* 元素数量
*/
private int n;
/**
* 索引二叉堆(元素=顶点v,对应keys[v]):pq[0]代表原点,其它顶点从pq[1]开始插入
*/
private int[] pq;
/**
* 标记索引为i的元素在二叉堆中的位置。pq的反转数组(qp[index]=i):qp[pq[i]] = pq[qp[i]] = i
*/
private int[] qp; /**
* 元素有序数组(按照pq的索引赋值)
*/
private Key[] keys; /**
* 初始化一个空索引优先队列,索引范围:0 ~ maxN-1
*
* @param maxN the keys on this priority queue are index from {@code 0}
* {@code maxN - 1}
* @throws IllegalArgumentException if {@code maxN < 0}
*/
public IndexMinPQ(int maxN) {
if (maxN < 0) throw new IllegalArgumentException();
this.maxN = maxN;
// 初始有0个元素
n = 0;
// 初始化键数组长度为maxN + 1
keys = (Key[]) new Comparable[maxN + 1];
// 初始化"键值对"数组长度为maxN + 1
pq = new int[maxN + 1];
// 初始化"值键对"数组长度为maxN + 1
qp = new int[maxN + 1];
// 遍历给"值键对"数组赋值-1,后续只要!=-1,即包含i
for (int i = 0; i <= maxN; i++)
qp[i] = -1;
} /**
* Returns true if this priority queue is empty.
*
* @return {@code true} if this priority queue is empty;
* {@code false} otherwise
*/
public boolean isEmpty() {
return n == 0;
} /**
* Is {@code i} an index on this priority queue?
*
* @param i an index
* @return {@code true} if {@code i} is an index on this priority queue;
* {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
*/
public boolean contains(int i) {
validateIndex(i);
return qp[i] != -1;
} /**
* Returns the number of keys on this priority queue.
*
* @return the number of keys on this priority queue
*/
public int size() {
return n;
} /**
* 插入一个元素,将元素key关联索引i
*
* @param i an index
* @param key the key to associate with index {@code i}
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @throws IllegalArgumentException if there already is an item associated
* with index {@code i}
*/
public void insert(int i, Key key) {
validateIndex(i);
if (contains(i)) throw new IllegalArgumentException("index is already in the priority queue");
// 元素个数+1
n++;
// 索引为i的二叉堆位置为n
qp[i] = n;
// 二叉堆底部插入新元素,值=i
pq[n] = i;
// 索引i对应的元素赋值
keys[i] = key;
// 二叉堆中,上浮最后一个元素(小值上浮)
swim(n);
} /**
* 返回最小元素的索引
*
* @return an index associated with a minimum key
* @throws NoSuchElementException if this priority queue is empty
*/
public int minIndex() {
if (n == 0) throw new NoSuchElementException("Priority queue underflow");
return pq[1];
} /**
* 返回最小元素(key)
*
* @return a minimum key
* @throws NoSuchElementException if this priority queue is empty
*/
public Key minKey() {
if (n == 0) throw new NoSuchElementException("Priority queue underflow");
return keys[pq[1]];
} /**
* 删除最小值key,并返回最小值
*
* @return an index associated with a minimum key
* @throws NoSuchElementException if this priority queue is empty
*/
public int delMin() {
if (n == 0) throw new NoSuchElementException("Priority queue underflow");
// pq[1]即为索引最小值
int min = pq[1];
// 交换第一个元素和最后一个元素
exch(1, n--);
// 把新换来的第一个元素下沉
sink(1);
// 校验下沉后,最后一个元素是最小值
assert min == pq[n+1];
// 恢复初始值,-1即代表该元素已删除
qp[min] = -1; // delete
// 方便垃圾回收
keys[min] = null;
// 最后一个元素(索引)赋值-1
pq[n+1] = -1; // not needed
return min;
} /**
* Returns the key associated with index {@code i}.
*
* @param i the index of the key to return
* @return the key associated with index {@code i}
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @throws NoSuchElementException no key is associated with index {@code i}
*/
public Key keyOf(int i) {
validateIndex(i);
if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
else return keys[i];
} /**
* Change the key associated with index {@code i} to the specified value.
*
* @param i the index of the key to change
* @param key change the key associated with index {@code i} to this key
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @throws NoSuchElementException no key is associated with index {@code i}
*/
public void changeKey(int i, Key key) {
validateIndex(i);
if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
keys[i] = key;
swim(qp[i]);
sink(qp[i]);
} /**
* Change the key associated with index {@code i} to the specified value.
*
* @param i the index of the key to change
* @param key change the key associated with index {@code i} to this key
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @deprecated Replaced by {@code changeKey(int, Key)}.
*/
@Deprecated
public void change(int i, Key key) {
changeKey(i, key);
} /**
* 减小索引i对应的值为key
* 更新:
* 1.元素数组keys[]
* 2.小顶二叉堆pq[]
*
* @param i the index of the key to decrease
* @param key decrease the key associated with index {@code i} to this key
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @throws IllegalArgumentException if {@code key >= keyOf(i)}
* @throws NoSuchElementException no key is associated with index {@code i}
*/
public void decreaseKey(int i, Key key) {
validateIndex(i);
if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
// key 值一样,报错
if (keys[i].compareTo(key) == 0)
throw new IllegalArgumentException("Calling decreaseKey() with a key equal to the key in the priority queue");
// key比当前值大,报错
if (keys[i].compareTo(key) < 0)
throw new IllegalArgumentException("Calling decreaseKey() with a key strictly greater than the key in the priority queue");
// key比当前值小,把key赋值进去
keys[i] = key;
// 小值上浮(qp[i]=索引i在二叉堆pq[]中的位置)
swim(qp[i]);
} /**
* Increase the key associated with index {@code i} to the specified value.
*
* @param i the index of the key to increase
* @param key increase the key associated with index {@code i} to this key
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @throws IllegalArgumentException if {@code key <= keyOf(i)}
* @throws NoSuchElementException no key is associated with index {@code i}
*/
public void increaseKey(int i, Key key) {
validateIndex(i);
if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
if (keys[i].compareTo(key) == 0)
throw new IllegalArgumentException("Calling increaseKey() with a key equal to the key in the priority queue");
if (keys[i].compareTo(key) > 0)
throw new IllegalArgumentException("Calling increaseKey() with a key strictly less than the key in the priority queue");
keys[i] = key;
sink(qp[i]);
} /**
* Remove the key associated with index {@code i}.
*
* @param i the index of the key to remove
* @throws IllegalArgumentException unless {@code 0 <= i < maxN}
* @throws NoSuchElementException no key is associated with index {@code i}
*/
public void delete(int i) {
validateIndex(i);
if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
int index = qp[i];
exch(index, n--);
swim(index);
sink(index);
keys[i] = null;
qp[i] = -1;
} // throw an IllegalArgumentException if i is an invalid index
private void validateIndex(int i) {
if (i < 0) throw new IllegalArgumentException("index is negative: " + i);
if (i >= maxN) throw new IllegalArgumentException("index >= capacity: " + i);
} /***************************************************************************
* General helper functions.
***************************************************************************/
private boolean greater(int i, int j) {
return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
} private void exch(int i, int j) {
int swap = pq[i];
pq[i] = pq[j];
pq[j] = swap;
qp[pq[i]] = i;
qp[pq[j]] = j;
} /***************************************************************************
* Heap helper functions.
***************************************************************************/
private void swim(int k) {
// 如果父节点值比当前节点值大,交换,父节点作为当前节点,轮询。即小值上浮。
while (k > 1 && greater(k/2, k)) {
exch(k, k/2);
k = k/2;
}
} private void sink(int k) {
while (2*k <= n) {
int j = 2*k;
if (j < n && greater(j, j+1)) j++;
if (!greater(k, j)) break;
exch(k, j);
k = j;
}
} /***************************************************************************
* Iterators.
***************************************************************************/ /**
* Returns an iterator that iterates over the keys on the
* priority queue in ascending order.
* The iterator doesn't implement {@code remove()} since it's optional.
*
* @return an iterator that iterates over the keys in ascending order
*/
@Override
public Iterator<Integer> iterator() { return new HeapIterator(); } private class HeapIterator implements Iterator<Integer> {
// create a new pq
private IndexMinPQ<Key> copy; // add all elements to copy of heap
// takes linear time since already in heap order so no keys move
public HeapIterator() {
copy = new IndexMinPQ<Key>(pq.length - 1);
for (int i = 1; i <= n; i++)
copy.insert(pq[i], keys[pq[i]]);
} @Override
public boolean hasNext() { return !copy.isEmpty(); }
@Override
public void remove() { throw new UnsupportedOperationException(); } @Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
return copy.delMin();
}
} /**
* Unit tests the {@code IndexMinPQ} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
// insert a bunch of strings
String[] strings = { "it", "was", "the", "best", "of", "times", "it", "was", "the", "worst" }; IndexMinPQ<String> pq = new IndexMinPQ<String>(strings.length);
for (int i = 0; i < strings.length; i++) {
pq.insert(i, strings[i]);
} // delete and print each key
while (!pq.isEmpty()) {
int i = pq.delMin();
StdOut.println(i + " " + strings[i]);
}
StdOut.println(); // reinsert the same strings
for (int i = 0; i < strings.length; i++) {
pq.insert(i, strings[i]);
} // print each key using the iterator
for (int i : pq) {
StdOut.println(i + " " + strings[i]);
}
while (!pq.isEmpty()) {
pq.delMin();
} }
}
3.2.2 最短路径
package study.algorithm.graph; import study.algorithm.base.In;
import study.algorithm.base.IndexMinPQ;
import study.algorithm.base.Stack;
import study.algorithm.base.StdOut; /***
* @Description 边权重非负的加权有向图的单起点最短路径树
* @author denny.zhang
* @date 2020/4/23 11:29 上午
*/
public class DijkstraSP { /**
* 最短路径数组,元素:到所有顶点的最短路径
*/
private double[] distTo; /**
* 有向边数组:最短路径最后一条边数组
*/
private DirectedEdge[] edgeTo; /**
* 顶点作为下标,索引最小优先级队列
*/
private IndexMinPQ<Double> pq; /**
* 计算从原点S 到 其它所有顶点 的"最短路径" 边权重 图
*
* @param G the edge-weighted digraph 边权重图
* @param s the source vertex 原点
* @throws IllegalArgumentException if an edge weight is negative
* @throws IllegalArgumentException unless {@code 0 <= s < V}
*/
public DijkstraSP(EdgeWeightedDigraph G, int s) {
// 负权重校验
for (DirectedEdge e : G.edges()) {
if (e.weight() < 0) {
throw new IllegalArgumentException("edge " + e + " has negative weight");
}
}
// 最短路径数组长度=顶点个数
distTo = new double[G.V()];
// 构造长度为顶点总数的最短路径边数组
edgeTo = new DirectedEdge[G.V()];
// 校验原点值
validateVertex(s);
// 初始化所有顶点的路径为无穷大
for (int v = 0; v < G.V(); v++) {
distTo[v] = Double.POSITIVE_INFINITY;
}
// 初始化到原点最小路径为0
distTo[s] = 0.0; // 构造一个长度为 顶点总数的 索引最小优先队列
pq = new IndexMinPQ<Double>(G.V());
// 把原点插入,路径为0
pq.insert(s, distTo[s]);
// 只要队列不空(从上往下,顺序遍历一遍pq[]),
while (!pq.isEmpty()) {
// 删除最小key(即pq[1]),并返回最小值(顶点)
int v = pq.delMin();
// 遍历顶点v的邻接表,每一条边
for (DirectedEdge e : G.adj(v)) {
// 放松边
relax(e);
}
} // 校验
assert check(G, s);
} /**
* 放松并更新pq
* @param e
*/
private void relax(DirectedEdge e) {
// 起点、终点
int v = e.from(), w = e.to();
// 如果原点到终点w的距离 > 原点到起点v的距离+边权重 说明原点到w松弛了
if (distTo[w] > distTo[v] + e.weight()) {
// 最新距离
distTo[w] = distTo[v] + e.weight();
// 到终点w的边赋值为新边
edgeTo[w] = e;
// 如果优先队列已经包含终点w
if (pq.contains(w)) {
// 比较下标为w的key如果>当前路径(即当前值比队列中值小),重新排序
pq.decreaseKey(w, distTo[w]);
} else {
// 不包含,插入并排序
pq.insert(w, distTo[w]);
}
}
} /**
* s->v的最短路径
* @param v the destination vertex
* @return the length of a shortest path from the source vertex {@code s} to vertex {@code v};
* {@code Double.POSITIVE_INFINITY} if no such path
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public double distTo(int v) {
validateVertex(v);
return distTo[v];
} /**
* s->v是否可达
*
* @param v the destination vertex
* @return {@code true} if there is a path from the source vertex
* {@code s} to vertex {@code v}; {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public boolean hasPathTo(int v) {
validateVertex(v);
return distTo[v] < Double.POSITIVE_INFINITY;
} /**
* s->v的最短可迭代边(1->2->3)
*
* @param v the destination vertex
* @return a shortest path from the source vertex {@code s} to vertex {@code v}
* as an iterable of edges, and {@code null} if no such path
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public Iterable<DirectedEdge> pathTo(int v) {
validateVertex(v);
if (!hasPathTo(v)) {
return null;
}
// 可迭代有向边栈
Stack<DirectedEdge> path = new Stack<DirectedEdge>();
// e是顶点v的最短路径树的最后一条边,沿着边往上追溯上一个顶点 3->2->1
for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
// 压栈
path.push(e);
}
return path;
} // check optimality conditions:
// (i) for all edges e: distTo[e.to()] <= distTo[e.from()] + e.weight()
// (ii) for all edge e on the SPT: distTo[e.to()] == distTo[e.from()] + e.weight()
private boolean check(EdgeWeightedDigraph G, int s) { // 校验边权重不为负值
for (DirectedEdge e : G.edges()) {
if (e.weight() < 0) {
System.err.println("negative edge weight detected");
return false;
}
} // 校验到顶点的路径为0且到顶点的边为空
if (distTo[s] != 0.0 || edgeTo[s] != null) {
System.err.println("distTo[s] and edgeTo[s] inconsistent");
return false;
}
// 遍历顶点
for (int v = 0; v < G.V(); v++) {
// 起点跳过
if (v == s) {
continue;
}
// 到顶点v的最后一条边为空(不可达) 且 到顶点v的最短路径不是无穷大(即有值)两者冲突
if (edgeTo[v] == null && distTo[v] != Double.POSITIVE_INFINITY) {
System.err.println("distTo[] and edgeTo[] inconsistent");
return false;
}
} // 校验所有边非松弛
for (int v = 0; v < G.V(); v++) {
// 遍历顶点v的邻接边
for (DirectedEdge e : G.adj(v)) {
int w = e.to();
// 校验松弛
if (distTo[v] + e.weight() < distTo[w]) {
System.err.println("edge " + e + " not relaxed");
return false;
}
}
} // 校验最短路径树:满足 distTo[w] == distTo[v] + e.weight()
for (int w = 0; w < G.V(); w++) {
// 跳过不可达顶点
if (edgeTo[w] == null) {
continue;
}
// 最后一条边
DirectedEdge e = edgeTo[w];
// 起点
int v = e.from();
//终点
if (w != e.to()) {
return false;
}
// 校验:最短路劲树,起点路径+权重=终点路径
if (distTo[v] + e.weight() != distTo[w]) {
System.err.println("edge " + e + " on shortest path not tight");
return false;
}
}
return true;
} // throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = distTo.length;
if (v < 0 || v >= V) {
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
} /**
* Unit tests the {@code DijkstraSP} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
// 图文件名称
In in = new In(args[0]);
// 构造边权重有向图
EdgeWeightedDigraph G = new EdgeWeightedDigraph(in);
// 顶点
int s = Integer.parseInt(args[1]); // 计算最短路径
DijkstraSP sp = new DijkstraSP(G, s); // 遍历所有顶点
for (int t = 0; t < G.V(); t++) {
// 可达
if (sp.hasPathTo(t)) {
// 原点到t的路径 长度
StdOut.printf("%d to %d (%.2f) ", s, t, sp.distTo(t));
// 原点到t的路径图
for (DirectedEdge e : sp.pathTo(t)) {
StdOut.print(e + " ");
}
// 换行
StdOut.println();
}
// 不可达
else {
StdOut.printf("%d to %d no path\n", s, t);
}
}
} }
四、测试结果
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)到 其它所有顶点 的"最短路径" 边权重 图:
运行的最短路径,结果如下:
最短路径树:Dijstra算法的更多相关文章
- 经典树与图论(最小生成树、哈夫曼树、最短路径问题---Dijkstra算法)
参考网址: https://www.jianshu.com/p/cb5af6b5096d 算法导论--最小生成树 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树. im ...
- 【算法设计与分析基础】25、单起点最短路径的dijkstra算法
首先看看这换个数据图 邻接矩阵 dijkstra算法的寻找最短路径的核心就是对于这个节点的数据结构的设计 1.节点中保存有已经加入最短路径的集合中到当前节点的最短路径的节点 2.从起点经过或者不经过 ...
- 最短路径问题---Dijkstra算法详解
侵删https://blog.csdn.net/qq_35644234/article/details/60870719 前言 Nobody can go back and start a new b ...
- 数据结构与算法--最短路径之Dijkstra算法
数据结构与算法--最短路径之Dijkstra算法 加权图中,我们很可能关心这样一个问题:从一个顶点到另一个顶点成本最小的路径.比如从成都到北京,途中还有好多城市,如何规划路线,能使总路程最小:或者我们 ...
- 25最短路径之Dijkstra算法
图的最优化问题:最小生成树.最短路径 典型的图应用问题 无向连通加权图的最小生成树 有向/无向加权图的最短路径 四个经典算法 Kruskal算法.Prim算法---------------最小生成树 ...
- 单源最短路Dijstra算法
Dijstra算法是寻找从某一顶点i出发到大其他顶点的最短路径.Distra算法的思想与Prim算法很像,它收录顶点的规则是按照路径长度递增的顺序收录的.设v0是源顶点,我们要寻找从v0出发到其他任意 ...
- BZOJ4016:[FJOI2014]最短路径树问题
浅谈树分治:https://www.cnblogs.com/AKMer/p/10014803.html 题目传送门:https://www.lydsy.com/JudgeOnline/problem. ...
- BZOJ 4016 最短路径树问题 最短路径树构造+点分治
题目: BZOJ4016最短路径树问题 分析: 大家都说这是一道强行拼出来的题,属于是两种算法的模板题. 我们用dijkstra算法算出1为源点的最短路数组,然后遍历一下建出最短路树. 之后就是裸的点 ...
- bzoj 4016: [FJOI2014]最短路径树问题
bzoj4016 最短路路径问题 Time Limit: 5 Sec Memory Limit: 512 MB Description 给一个包含n个点,m条边的无向连通图.从顶点1出发,往其余所有点 ...
随机推荐
- 支持向量机SVM推导
样本(\(x_{i}\),\(y_{i}\))个数为\(m\): \[\{x_{1},x_{2},x_{3}...x_{m}\} \] \[\{y_{1},y_{2},y_{3}...y_{m}\} ...
- 008-进制-C语言笔记
008-进制-C语言笔记 学习目标 1.[掌握]include预处理指令 2.[掌握]多文件开发 3.[了解]认识进制 4.[掌握]进制之间的互相转换 5.[掌握]原码,反码,补码 6.[掌握]位运算 ...
- L17 AlexNet VGG NiN GoogLeNet
深度卷积神经网络(AlexNet) LeNet: 在大的真实数据集上的表现并不尽如⼈意. 1.神经网络计算复杂. 2.还没有⼤量深⼊研究参数初始化和⾮凸优化算法等诸多领域. 机器学习的特征提取:手工定 ...
- tensorflow-参数、超参数、卷积核权值共享
根据网上查询到的说法,参数就是在卷积神经网络中可以被训练的参数,比如卷积核的权值和偏移等等,而超参数是一些预先设定好并且无法改变的,比如说是卷积核的个数等. 另外还有一个最最基础的概念,就是卷积核的权 ...
- 深入浅出node.js游戏服务器开发1——基础架构与框架介绍
2013年04月19日 14:09:37 MJiao 阅读数:4614 深入浅出node.js游戏服务器开发1——基础架构与框架介绍 游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的 ...
- vs 类型定义及语句,随机数
1 类型定义: 1)小数: 类型 变量名 赋值 decimal d : d=1.2m float ...
- 串匹配问题 (KMP算法) 详解
串这个概念对于我们学到现在的水平来说应该是经历颇丰了,因为在C语言中我们所用到的"串"知识是在字符串那里,有了这个概念,我们再去学习串就相对而言轻松多了. 那么,现在来介绍一下字符 ...
- 设计模式 - 模板方法模式详解及其在Spring中的应用
基本介绍 模板方法模式(Template Method Pattern)也叫模板模式,它在一个抽象类中公开定义了执行它的方法的模板,它的字类可以按需重写方法实现,但调用将以抽象类中定义的方式进行. 简 ...
- CLDAPReflectionDDoS(CLDAP反射放大攻击)
CLDAP Reflection DDoS 0x01 LDAP: 全称为Lightweight Directory Access Protocol,即轻量目录访问协议,基于X.500标准: 目录服务就 ...
- Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节
简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...