基本概念
要找出最短路径,其实就是从起点遍历所有能到达的顶点,然后计算他们的权重。Dijkstra算法核心在于边的松弛(relax),可以想象成一根绷紧的橡皮筋,让它放松下来。即是计算源点(s)经过当前点(v)到目标点(w)的权重,如果比目标点(w)之前的权重要小,就替换掉。最终的结果就是生成一颗最小路径树。这个算法和prim算法非常相似,甚至就是prim即时算法的变种。如果加权无向图和加权有向图的边和权重对应,最短路径树和最小生成树其实是等价的。
Dijkstra算法并不能处理权重为负数的边。
实现
distTo初始为无穷大意味着还没有访问过。如果顶点访问完后也是无穷大,就意味着不能到达。
/** * 迪杰斯塔拉算法 * @author yuli * */
public class Dijkstra implements Paths{
private Edge[] edgeTo;//记录边的路径,从起始点到某个点,
private double[] distTo;//到这个顶点的权重
private int s;//开始的顶点
private IndexMinPQ<Double> pq;//用来保存到最短路径树的最小边,也就是离最短路径树最近的边
public Dijkstra(EdgeWeightedDiGraph graph,int s) {
edgeTo = new Edge[graph.getV()];//初始化顶点路径
distTo = new double[graph.getV()];//初始化到这个顶点的距离
pq= new IndexMinPQ<>(graph.getV());
this.s = s;
//将起点到这个顶点的距离初始化为无穷大。
for(int i = 0;i<graph.getV();i++){
distTo[i] = Double.POSITIVE_INFINITY;
}
distTo[0]= 0.0d;
pq.insert(s, 0.0);
while(!pq.isEmpty()){
relax(graph,pq.delMin());
}
}
/** * 松弛边,松弛就是让权重变小 */
private void relax(EdgeWeightedDiGraph graph,int v){
Iterable<Edge> adj = graph.adj(v);
for (Edge edge : adj) {
int w = edge.to();//要松弛的目标顶点
//如果目标顶点的权重大于当前顶点的权重+当前边的权重,就替换
if(distTo[w] > distTo[v] + edge.getWeight()){
distTo[w] = distTo[v] + edge.getWeight();
edgeTo[w] = edge;//到这条边的最小权重
//通过索引优先队列来减少比较次数
if(pq.contains(w)){
pq.changeKey(w, distTo[w]);;
}else{
pq.insert(w, distTo[w]);
}
}
}
}
/** * 取得最短路径 * @param v * @return */
public Iterable<Integer> pathTo(int v){
if(hasPathTo(v)){
throw new RuntimeException("没有此路径");
}
Stack<Integer> stack = new Stack<>();
while(v != s){
Edge e = edgeTo[v];
stack.push(v);
v = e.form();
}
stack.push(s);
return stack;
}
/** * 获取最短路径的权重权重 * @return */
public double weight(int v){
return distTo[v];
}
public boolean hasPathTo(int v) {
return distTo[v] == Double.POSITIVE_INFINITY;
}
}