Dijkstra算法原理:s到w顶点,可以通过其他顶点中转到达并缩短距离,就松弛s到w的路径;
demo中使用的测试数据,来自《算法4》:
《算法4》配图里面的最短路径权重数据有小许误差,可自行手动验证看下:
用到的数据结构:
邻接边类 DirectedEdge.java
/**
* 边的对象
*
* @author ltx
*/
public class DirectedEdge implements Comparable<DirectedEdge> {
public int v; //边的起点-顶点v
public int w; //边的终点-顶点w
public double weight; //权重
public DirectedEdge(int v, int w, double weight) {
this.v = v;
this.w = w;
this.weight = weight;
}
/**
* 边的另外一个顶点
*
* @param v 顶点v
* @return
*/
public int other(int v) {
if (this.v == v) {
return this.w;
} else if (this.w == v) {
return this.v;
}
throw new RuntimeException("边不对!");
}
/**
* 实现compareTo方法,集合排序用
*
* @param that
* @return
*/
@Override
public int compareTo(DirectedEdge that) {
if (this.weight > that.weight) {
return 1;
} else if (this.weight < that.weight) {
return -1;
}
return 0;
}
@Override
public String toString() {
return String.format("%d->%d %.2f", v, w, weight);
}
}
加权无向图类 DirectedEdgeWeightedGraph.java
/**
* 加权有向图
*
* @author ltx
*/
public class DirectedEdgeWeightedGraph {
public int V; //顶点数量
public int E; //边数量
public List<DirectedEdge>[] adj; //邻接边
/**
* 初始化加权有向图
*
* @param V 顶点数
*/
public DirectedEdgeWeightedGraph(int V) {
//初始化顶点数
this.V = V;
//初始化边数
this.E = 0;
//初始化邻接表
this.adj = new List[V];
for (int i = 0; i < V; i++) {
adj[i] = new ArrayList<>();
}
}
/**
* 添加边
*
* @param e 边
*/
public void addEdge(DirectedEdge e) {
adj[e.v].add(e);
E++;
}
/**
* 打印邻接表结构
*/
public void show() {
System.out.printf("%d 个顶点, %d 条边\n", V, E);
for (int i = 0; i < V; i++) {
System.out.println(i + ": " + adj[i]);
}
}
/**
* 初始化加权有向图
*
* @return
*/
public static DirectedEdgeWeightedGraph init() {
/**
* 算法4里面的加权有向图
*/
DirectedEdgeWeightedGraph directedEdgeWeightedGraph = new DirectedEdgeWeightedGraph(8);
directedEdgeWeightedGraph.addEdge(new DirectedEdge(4, 5, 0.35));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(5, 4, 0.35));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(4, 7, 0.37));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(5, 7, 0.28));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(7, 5, 0.28));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(5, 1, 0.32));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(0, 4, 0.38));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(0, 2, 0.26));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(7, 3, 0.39));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(1, 3, 0.29));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(2, 7, 0.34));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(6, 2, 0.40));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(3, 6, 0.52));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(6, 0, 0.58));
directedEdgeWeightedGraph.addEdge(new DirectedEdge(6, 4, 0.93));
return directedEdgeWeightedGraph;
}
}
顶点和权重类 Vertex.java
见上一章节《《算法4》加权无向图最小生成树算法(Prime算法(延时实现) | Prime算法(即时实现) | Kruskal)》
Dijkstra算法实现类 DijkstraSP.java
/**
* 最短路径的Dijkstra算法
* 原理-s到w顶点,可以通过其他顶点中转到达并缩短距离,就松弛s到w的路径
*
* @author ltx
*/
public class DijkstraSP {
private DirectedEdgeWeightedGraph graph;
private DirectedEdge[] edgeTo; //实时动态存-顶点s到顶点w的最短路径的最后一条边
private double[] distTo; //实时动态存-顶点s到顶点w的最短路径权重和
private List<Vertex> pq; //顶点队列
public List<DirectedEdge> sp; //最短路径sp所有的边
private int s; //起点s
/**
* s到v的距离,不存在则返回无穷大
*
* @param v 顶点
* @return
*/
public double distTo(int v) {
System.out.printf("%d->%d的最短距离: \n", s, v);
return distTo[v];
}
/**
* 是否存在顶点s到顶点v的路径
*
* @param v 顶点
* @return
*/
public boolean hasPathTo(int v) {
System.out.printf("是否存在%d->%d的路径: \n", s, v);
return distTo[v] < Double.POSITIVE_INFINITY;
}
/**
* s到v的最短路径,没有则返回null
*
* @param v 顶点
* @return
*/
public List<DirectedEdge> pathTo(int v) {
System.out.printf("%d->%d的最短路径: \n", s, v);
List<DirectedEdge> path = new ArrayList<>();
Stack<DirectedEdge> st = new Stack<>();
//最后一条路径
DirectedEdge temp = edgeTo[v];
//放最后一条路径
st.push(temp);
while (temp != null && temp.v != s) {
//放中间的路径
st.push(edgeTo[temp.v]);
temp = edgeTo[temp.v];
}
//栈逆序到list,构成路径
while (!st.isEmpty()) {
path.add(st.pop());
}
return path;
}
/**
* 初始化最短路径sp
*
* @param graph 加权有向图
*/
public DijkstraSP(DirectedEdgeWeightedGraph graph, int s) {
this.graph = graph;
this.s = s;
edgeTo = new DirectedEdge[graph.V];
distTo = new double[graph.V];
//将顶点v到顶点s的权重初始化为最大
for (int i = 0; i < graph.V; i++) {
//正无穷的常数
distTo[i] = Double.POSITIVE_INFINITY;
}
pq = new LinkedList<>();
sp = new ArrayList<>();
//从顶点s开始
distTo[s] = 0;
pq.add(new Vertex(s, 0));
while (!pq.isEmpty()) {
//按权重排个序
Collections.sort(pq);
Vertex minV = pq.remove(0);
//顶点松弛
relax(minV.v);
}
}
/**
* 顶点松弛
*
* @param v 顶点v
*/
private void relax(int v) {
//检测顶点v的所有邻接边,并做松弛
for (DirectedEdge e : graph.adj[v]) {
int w = e.other(v);
//起点s通过v顶点到达w顶点,比之前通过其他顶点到达顶点w距离的近,就做一次边松弛
if (distTo[w] > distTo[v] + e.weight) {
//v-w路径添加到路径数组中
edgeTo[w] = e;
//将起点s到顶点w的路径修改为当前最短(实时修改)
distTo[w] = distTo[v] + e.weight;
//如果队列里面有顶点w,则修改s到w的距离权重
//Vertex重写了equals,可直接用w对比
Vertex node = new Vertex(w, distTo[w]);
if (pq.contains(node)) {
Vertex temp = pq.get(pq.indexOf(node));
temp.weight = e.weight;
} else {
//没有就新增
pq.add(node);
}
}
}
}
}
测试方法:
@Test
void sp() {
System.out.println("################加权有向图################");
DirectedEdgeWeightedGraph graph = DirectedEdgeWeightedGraph.init();
graph.show();
System.out.println("-------Dijkstra算法-------");
DijkstraSP sp = new DijkstraSP(graph, 0);
System.out.println(sp.hasPathTo(6));
System.out.println(sp.pathTo(6));
System.out.printf("%.2f\n", sp.distTo(6));
}
测试结果:
################加权有向图################
8 个顶点, 15 条边
0: [0->4 0.38, 0->2 0.26]
1: [1->3 0.29]
2: [2->7 0.34]
3: [3->6 0.52]
4: [4->5 0.35, 4->7 0.37]
5: [5->4 0.35, 5->7 0.28, 5->1 0.32]
6: [6->2 0.40, 6->0 0.58, 6->4 0.93]
7: [7->5 0.28, 7->3 0.39]
-------Dijkstra算法-------
是否存在0->6的路径:
true
0->6的最短路径:
[0->2 0.26, 2->7 0.34, 7->3 0.39, 3->6 0.52]
0->6的最短距离:
1.51