图论算法(三)最短路径

最短路径

定义:在一幅加权有向图中,从顶点s到顶点t的最短路径是所有从s到t的路径中的权重最小者。

Dijkstra 算法

Dijkstra算法会生成一颗最短路径树,树的根为起始顶点s, 树的分支为从顶点s到图G中所有其他顶点的最短路径。此算法要求图中的所有权值均为非负数。与Prim算法类似,Prim算法每次添加的都是离树最近的非树顶点,Dijkstra算法每次添加的都是离起点最近的非树顶点。

首先将distTo[s]初始化为0,distTo[]中的其他元素初始化为正无穷,然后将distTo[]最小的非树顶点放松并加入树中,如此这般,直到所有的顶点都在树中或者所有的非树顶点的distTo[]均为无穷大。

Dijkstra算法示例演示:

从顶点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;
    }
}

其它最短路径算法学习链接

图论算法(一)深度优先搜索与广度优先搜索
图论算法(二)最小生成树
图论算法(三)最短路径

你可能感兴趣的:(数据结构)