定义:在一幅加权有向图中,从顶点s到顶点t的最短路径是所有从s到t的路径中的权重最小者。
Dijkstra算法会生成一颗最短路径树,树的根为起始顶点s
, 树的分支为从顶点s
到图G
中所有其他顶点的最短路径。此算法要求图中的所有权值均为非负数。与Prim
算法类似,Prim
算法每次添加的都是离树最近的非树顶点,Dijkstra
算法每次添加的都是离起点最近的非树顶点。
首先将distTo[s]初始化为0,distTo[]中的其他元素初始化为正无穷,然后将distTo[]最小的非树顶点放松并加入树中,如此这般,直到所有的顶点都在树中或者所有的非树顶点的distTo[]均为无穷大。
从顶点v1到其他各个顶点的最短路径
首先将数组distTo[]中起始点v1的值设置为0,然后再查找一个离v1顶点最近的顶点。
我们的顶点集T的初始化为:T={v1}
既然是求 v1顶点到其余各个顶点的最短路程,那就先找一个离 v1 顶点最近的顶点。通过数组 DistTo 可知当前离v1顶点最近是 v3顶点。当选择了 v3 号顶点后,DistTo[2](下标从0开始)的值就已经从“估计值”变为了“确定值”,即 v1顶点到 v3顶点的最短路程就是当前 DistTo[2]值。将V3加入到T中。
为什么呢?因为目前离 v1顶点最近的是 v3顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 v1顶点到 v3顶点的路程进一步缩短了。因为 v1顶点到其它顶点的路程肯定没有 v1到 v3顶点短.
OK,既然确定了一个顶点的最短路径,下面我们就要根据这个新入的顶点V3会有出度,发现以v3 为弧尾的有: < v3,v4 >,那么我们看看路径:v1–v3–v4的长度是否比v1–v4短,其实这个已经是很明显的了,因为DistTo[3]代表的就是v1–v4的长度为无穷大,而v1–v3–v4的长度为:10+50=60,所以更新DistTo[3]的值,得到如下结果:
因此 DistTo[3]要更新为 60。这个过程有个专业术语叫做“松弛”。即 v1顶点到 v4顶点的路程即 DistTo[3],通过 < v3,v4> 这条边松弛成功。这便是 Dijkstra 算法的主要思想:通过“边”来松弛v1顶点到其余各个顶点的路程。
然后,我们又从除DistTo[2]和DistTo[0]外的其他值中寻找最小值,发现DistTo[4]的值最小,通过之前是解释的原理,可以知道v1到v5的最短距离就是DistTo[4]的值,然后,我们把v5加入到集合T中,然后,考虑v5的出度是否会影响我们的数组DistTo的值,v5有两条出度:< v5,v4>和 < v5,v6>,然后我们发现:v1–v5–v4的长度为:50,而DistTo[3]的值为60,所以我们要更新DistTo[3]的值.另外,v1-v5-v6的长度为:90,而DistTo[5]为100,所以我们需要更新DistTo[5]的值。更新后的DistTo数组如下图:
然后,继续从dis中选择未确定的顶点的值中选择一个最小的值,发现dis[3]的值是最小的,所以把v4加入到集合T中,此时集合T={v1,v3,v5,v4},然后,考虑v4的出度是否会影响我们的数组DistTo的值,v4有一条出度:< v4,v6>,然后我们发现:v1–v5–v4–v6的长度为:60,而dis[5]的值为90,所以我们要更新DistTo[5]的值,更新后的DistTo数组如下图:
然后,我们使用同样原理,分别确定了v6和v2的最短路径,最后dis的数组的值如下:
因此,从图中,我们可以发现v1-v2的值为:∞,代表没有路径从v1到达v2。
package chapter4;
public class DirectedEdge {
private final int v;//边的起点
private final int w;//边的终点
private final double weight;//边的权重
public DirectedEdge(int v,int w,double weight){
this.v=v;
this.w=w;
this.weight=weight;
}
public double weight(){
return weight;
}
public int from(){
return v;
}
public int to(){
return w;
}
}
package chapter4;
import edu.princeton.cs.algs4.Bag;
import edu.princeton.cs.algs4.In;
public class EdgeWeightedDigraph {
private final int V;//顶点总数
private int E;//边的总数
private Bag[] adj;//邻接表
public EdgeWeightedDigraph(int V){
this.V=V;
this.E=0;
adj=(Bag[])new Bag[V];
for(int v=0;v();
}
}
public EdgeWeightedDigraph(In in){
//TODO
}
public int V(){
return V;
}
public int E(){
return E;
}
public void addEdge(DirectedEdge e){
adj[e.from()].add(e);
E++;
}
public Iterable adj(int v){
return adj[v];
}
public Iterable edges(){
Bag bag = new Bag<>();
for(int v=0;v
package chapter4;
import edu.princeton.cs.algs4.DirectedEdge;
import edu.princeton.cs.algs4.EdgeWeightedDigraph;
import edu.princeton.cs.algs4.IndexMinPQ;
import edu.princeton.cs.algs4.Stack;
public class DijkstraSP {
private DirectedEdge[] edgeTo;//存放最小路径的边
private double[] distTo;//存放每个顶点到起点的最短距离
private IndexMinPQ pq;//处理的优先队列
public DijkstraSP(EdgeWeightedDigraph G, int s){
edgeTo=new DirectedEdge[G.V()];
distTo=new double[G.V()];
pq=new IndexMinPQ<>(G.V());
//初始化每个顶点到起点的距离为正无穷
for (int v = 0; v < G.V(); v++) {
distTo[v]=Double.POSITIVE_INFINITY;
}
//将起点到起点的距离设置为0,并且加入到优先队列中
distTo[s]=0;
pq.insert(s,0.0);
if(!pq.isEmpty()){
relax(G,pq.delMin());
}
}
/**
* 松弛顶点
* @param G
* @param v
*/
private void relax(EdgeWeightedDigraph G,int v){
//顶点v进入松弛阶段,其edgeTo[v]的值和distTo[v]的值就不会再变动了
//也就是加入到树中。
for (DirectedEdge edge : G.adj(v)) {
int w=edge.to();
if(distTo[w]>distTo[v]+edge.weight()){
//如果w顶点已经被加入到树中,则不可能进入到这个if块中
distTo[w]=distTo[v]+edge.weight();
edgeTo[w]=edge;
if(pq.contains(w)){
pq.changeKey(w,distTo[w]);
}else{
pq.insert(w,distTo[w]);
}
}
}
}
public double distTo(int v){
return distTo[v];
}
public boolean hasPathTo(int v){
return distTo[v] pathTo(int v){
if(!hasPathTo(v)){
return null;
}
Stack stack = new Stack<>();
for(DirectedEdge e = edgeTo[v];e!=null;e=edgeTo[e.from()]){
stack.push(e);
}
return stack;
}
}
其它最短路径算法学习链接
图论算法(一)深度优先搜索与广度优先搜索
图论算法(二)最小生成树
图论算法(三)最短路径