最短路径和
最短路径是指两点之间权值之和最小的路径之和,广泛应用于路径规划问题,有向图、无向图均适用,但不能有负权环:
上图C - D - F之间构成了负权环,A 到 F之间的最短路径A -> C -> D -> F -> C -> D -> F…可以无限缩短,即不存在最短路径。
单源最短路径最经典的实现有Dijkstra、Bellman-Ford两种算法。
Dijkstra算法实现
使用前提:单源最短路径,不能有负权边
算法原理:按路径长度递增的次序依次产生最短路径
时间复杂度:O(ElogV)
以下图为例,求A到个点的最短路径:
举一个贴近生活的例子:将连在一起的一堆石头以某一个为源点向上顺势拧起,直至石头全被提起。Dijkstra算法实现原理和拧石头类似,后离开桌面的石头一定是被先离开桌面的石头提起的,并且当可能被多条绳子提起时,只有最短那根绳子才能将其提起。
算法模拟
Dijkstra算法实现过程;
黑色:源点
红色:当前正在被更新最短路径的点
紫色:求出最短路径的点
首先,A到A的最短路径为0,将A提起,下一步有可能被提起的点是与A直接相连的点,所以对A的所有出度的边进行松弛操作,即更新B D E的最短路径:
扫描路径和列,路径和最小的即是下一个被提起的点,求出了该点 (B点) 的最短路径。下一个有可能被提起的点必然是与A、C直接相连的点,对B的所有出度进行松弛操作,更新与B直接相连的点C的最短路径:
同样扫描路径和列(标位紫色的是已经求出最短路径的点,不再扫描),找出这轮最短路径点D点,更新与D相连的点E、C的最短路径:
同样扫描,找出这轮的最短路径是C点,更新与C直接相连的E点的最短路径:
再次扫描,只有E点一点,找出E点最短路径,到此求出了所有点的最短路径:
封装
封装边:
private static class Edge<V, E>{
E weight;
Vertex<V, E> from; //源顶点
Vertex<V, E> to; //目的顶点
Edge(Vertex<V, E> from, Vertex<V, E> to){
this.from = from;
this.to = to;
}
Edge(Vertex<V, E> from, Vertex<V, E> to, E weight){
this(from, to);
this.weight = weight;
}
//!!!边集用Set、顶点集Map存储 底层都是hash表+链表+红黑树 必须在Edge、Vertex类里实现各自的HashCode和equals方法
@Override
public boolean equals(Object obj) {
Edge<V, E> edge = (Edge<V, E>)obj;
//如果两条边源顶点和目的顶点都相同则视为同一条边
return Objects.equals(this.from, edge.from) && Objects.equals(this.to, edge.to);
}
@Override
public int hashCode() {
return from.hashCode() * 31 + to.hashCode();
}
}
封装顶点:
private static class Vertex<V, E>{
V value;
Set<Edge<V, E>> inEdges = new HashSet<>();
Set<Edge<V, E>> outEdges = new HashSet<>();
Vertex(V value){
this.value = value;
}
@Override
public boolean equals(Object obj) {
return Objects.equals(value, ((Vertex<V, E>)obj).value);
}
@Override
public int hashCode() {
return value == null ? 0 : value.hashCode();
}
}
封装路径信息:
public static class PathInfo<V, E>{
private E weight;
private List<Edge<V, E>> path = new LinkedList<>(); //用链表存储路径
public String getPathInfo() {
StringBuilder s = new StringBuilder();
for (Edge<V, E> edge : path) {
s = s.append(edge.from.value +"-->" + edge.to.value + " "