每日一省之————加权有向图的最短路径问题

1 加权有向图中边的数据结构


/**
 * 该类用于表示有向图中的一条有向边
 * @author lhever 2017年3月2日 下午11:25:30
 * @version v1.0
 */
public class DirectedEdge
{
    private final int v;
    private final int w;
    private final double weight;

    /**
     * 有向边对象的构造函数
     * 
     * @param v
     *            起点
     * @param w
     *            终点
     * @param weight
     *            边的权重
     * @author lhever 2017年3月2日 下午11:26:05
     * @since v1.0
     */
    public DirectedEdge(int v, int w, double weight)
    {
        if (v < 0)
        {
            throw new IllegalArgumentException("起始顶点名必须是非负整数");
        }
        if (w < 0)
        {
            throw new IllegalArgumentException("终点名必须是非负整数");
        }
        if (Double.isNaN(weight))
        {
            throw new IllegalArgumentException("权重不合法");
        }
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    /**
     * 获取起始顶点
     * @return
     * @author lhever 2017年3月2日 下午11:29:32
     * @since v1.0
     */
    public int from()
    {
        return v;
    }

    /**
     * 获取结束顶点
     * 
     * @return
     * @author lhever 2017年3月2日 下午11:29:55
     * @since v1.0
     */
    public int to()
    {
        return w;
    }

    /**
     * 获取权重
     * @return
     * @author lhever 2017年3月2日 下午11:30:15
     * @since v1.0
     */
    public double weight()
    {
        return weight;
    }

    public String toString()
    {
        return v + "->" + w + " " + String.format("%5.2f", weight);
    }

    /**
     * 测试
     * @param args
     * @author lhever 2017年3月2日 下午11:30:37
     * @since v1.0
     */
    public static void main(String[] args)
    {
        DirectedEdge e = new DirectedEdge(12, 34, 5.67);
        System.out.println(e);
    }
}

2 加权有向图的数据结构


import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 该类抽象了加权有向图这种数据结构
 * @author xxx 2017年3月2日 下午11:34:33
 * @version v1.0
 */
public class EdgeWeightedDigraph {


    private static final String NEWLINE = System.getProperty("line.separator");

    private final int V;                // 有向图中的顶点数目
    private int E;                      // 有向图中边的数目
    private List[] adj;    // adj[v] 的值表示顶点v的邻接边的列表
    private int[] indegree;             // indegree[v] 的值是顶点v的入度

    private static long seed; 
    private static Random random; 

    static
    {
        seed = System.currentTimeMillis();
        random = new Random(seed);
    }

    /**
     * 初始化加权有向图对象,构造函数需要指定图中的顶点总数
     * @param V
     * @author xxx 2017年3月2日 下午11:37:03
     * @since v1.0
     */
    @SuppressWarnings("unchecked")
    public EdgeWeightedDigraph(int V)
    {
        if (V < 0)
        {
            throw new IllegalArgumentException("加权有向图中的顶点数必须是非负数");
        }
        this.V = V;
        this.E = 0;
        this.indegree = new int[V];
        adj = (List[]) new ArrayList[V];
        for (int v = 0; v < V; v++)
        {
            adj[v] = new ArrayList();
        }
    }

    /**
     * 初始化一个有向图,初始化的有向图有V个顶点,E条边
     * @param V
     * @param E
     * @author xxx 2017年3月2日 下午11:40:05
     * @since v1.0
     */
    public EdgeWeightedDigraph(int V, int E)
    {
        this(V);
        if (E < 0)
        {
            throw new IllegalArgumentException("加权有向图中的顶点数必须是非负数");
        }
        for (int i = 0; i < E; i++)
        {
            int v = random.nextInt(V);
            int w = random.nextInt(V);
            double weight = 0.01 * random.nextInt(100);
            DirectedEdge e = new DirectedEdge(v, w, weight);
            addEdge(e);
        }
    }

    /**
     * 使用一个已知的加权有向图实例化另外一个加权优先图,深拷贝?
     * @param G
     * @author xxx 2017年3月2日 下午11:45:11
     * @since v1.0
     */
    public EdgeWeightedDigraph(EdgeWeightedDigraph G)
    {
        this(G.V());
        this.E = G.E();
        for (int v = 0; v < G.V(); v++)
        {
            this.indegree[v] = G.indegree(v);
        }
        for (int v = 0; v < G.V(); v++)
        {
            List edgeList = new ArrayList();
            for (DirectedEdge e : G.adj[v])
            {
                edgeList.add(e);
            }
            for (DirectedEdge e : edgeList)
            {
                adj[v].add(e);
            }
        }
    }

    /**
     * 返回顶点总数
     * @return
     * @author xxx 2017年3月2日 下午11:48:06
     * @since v1.0
     */
    public int V()
    {
        return V;
    }

    /**
     * 返回边的总数
     * @return
     * @author xxx 2017年3月2日 下午11:48:24
     * @since v1.0
     */
    public int E()
    {
        return E;
    }

    private void validateVertex(int v)
    {
        if (v < 0 || v >= V) 
        {
            throw new IllegalArgumentException("顶点 " + v + " 必须介于 0 和 " + (V - 1) + " 之间");
        }
    }

     /**
     * 添加一条边
     * @param e
     * @author xxx 2017年3月2日 下午11:49:51
     * @since v1.0
     */
    public void addEdge(DirectedEdge e)
    {
        int v = e.from();
        int w = e.to();
        validateVertex(v);
        validateVertex(w);
        adj[v].add(e);
        indegree[w]++;
        E++;
    }

    /**
     * 获取起点是v的所有有向边
     * @param v
     * @return
     * @author xxx 2017年3月2日 下午11:50:36
     * @since v1.0
     */
    public Iterable adj(int v)
    {
        validateVertex(v);
        return adj[v];
    }

    /**
     * 获取起点是v的所有有向边的总数,也即获取顶点v的出度
     * @return
     * @author xxx 2017年3月2日 下午11:50:36
     * @since v1.0
     */
    public int outdegree(int v)
    {
        validateVertex(v);
        return adj[v].size();
    }

    /**
     * 获取终点是v的所有有向边的总数,也即获取顶点v的入度
     * @return
     * @author xxx 2017年3月2日 下午11:50:36
     * @since v1.0
     */
    public int indegree(int v)
    {
        validateVertex(v);
        return indegree[v];
    }

    /**
     * 获取图中的所有有向边
     * @return
     * @author xxx 2017年3月2日 下午11:52:46
     * @since v1.0
     */
    public Iterable edges()
    {
        List list = new ArrayList();
        for (int v = 0; v < V; v++)
        {
            for (DirectedEdge e : adj(v))
            {
                list.add(e);
            }
        }
        return list;
    }

    @Override
    public String toString()
    {
        StringBuilder s = new StringBuilder();
        s.append(V + " " + E + NEWLINE);
        for (int v = 0; v < V; v++)
        {
            s.append(v + ": ");
            for (DirectedEdge e : adj[v])
            {
                s.append(e + "  ");
            }
            s.append(NEWLINE);
        }
        return s.toString();
    }

    /**
     * 测试
     * @param args
     * @author xxx 2017年3月2日 下午11:53:37
     * @since v1.0
     */
    public static void main(String[] args)
    {
        EdgeWeightedDigraph g = new EdgeWeightedDigraph(5, 8);
        System.out.println(g);
    }

}

3 解决边的权重全部非负的最短路径问题的Dijikstra算法


import java.util.Stack;

/**
 * 解决加权有向图中单点最短路径的Dijkstra算法,该算法要求图中边的权重是非负
 * @author xxx 2017年3月3日 上午12:02:22
 * @version v1.0
 */
public class DijkstraSP 
{
    private double[] distTo;          // distTo[v]的值表示从起点s到终点v的最短路径
    private DirectedEdge[] edgeTo;    // edgeTo[v]的值表示从起点s到终点v的最短路径中的最后一条边
    private IndexMinPQ pq;    // 用于存储顶点v的索引优先队列


    /**
     * 计算加权有向图G中从起点s到图中其他所有顶点的最短路径
     * @param G
     * @param s
     * @author xxx 2017年3月3日 上午12:07:11
     * @since v1.0
     */
    public DijkstraSP(EdgeWeightedDigraph G, int s)
    {
        for (DirectedEdge e : G.edges())
        {
            if (e.weight() < 0)
            {
                throw new IllegalArgumentException("边 " + e + " 的权重为非负数,该算法不适用");
            }
        }

        distTo = new double[G.V()];
        edgeTo = new DirectedEdge[G.V()];
        for (int v = 0; v < G.V(); v++)
        {
            distTo[v] = Double.POSITIVE_INFINITY;
        }
        distTo[s] = 0.0;

        pq = new IndexMinPQ(G.V());
        pq.insert(s, distTo[s]);
        while (!pq.isEmpty())
        {
            int v = pq.delMin();
            for (DirectedEdge e : G.adj(v)) 
            {
                relax(e);
            }
        }

        // 验证
        assert check(G, s);
    }


    /**
     * 采用边的松弛算法,修正到各个终点的最短距离,同时不断修正到各个终点的最短路径上的最后一条边。
     * @param e
     * @author xxx 2017年3月3日 上午12:14:34
     * @since v1.0
     */
    private void relax(DirectedEdge e)
    {
        int v = e.from(), w = e.to();
        if (distTo[w] > distTo[v] + e.weight())
        {
            distTo[w] = distTo[v] + e.weight();
            edgeTo[w] = e;
            if (pq.contains(w))
            {
                pq.decreaseKey(w, distTo[w]);
            } else
            {
                pq.insert(w, distTo[w]);
            }
        }
    }


    /**
     * 返回起点s到终点v的最短距离(权重)
     * @param v
     * @return
     * @author xxx 2017年3月3日 上午12:14:53
     * @since v1.0
     */
    public double distTo(int v)
    {
        return distTo[v];
    }


    /**
     * 判断是否一条从起点s到终点v的路径
     * @param v
     * @return
     * @author xxx 2017年3月3日 上午12:15:51
     * @since v1.0
     */
    public boolean hasPathTo(int v)
    {
        return distTo[v] < Double.POSITIVE_INFINITY;
    }


    /**
     * 返回从起点s到终点v的一条最短路径,如果存在的话
     * @param v
     * @return
     * @author xxx 2017年3月3日 上午12:17:33
     * @since v1.0
     */
    public Iterable pathTo(int v)
    {
        if (!hasPathTo(v))
        {
            return null;
        }
        Stack path = new Stack();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()])
        {
            path.push(e);
        }
        return path;
    }


    /**
     * 验证算法求得的最短路径是不是确实最短
     * @param G
     * @param s
     * @return
     * @author xxx 2017年3月3日 上午12:19:34
     * @since v1.0
     */
    private boolean check(EdgeWeightedDigraph G, int s)
    {

        // 验证边权重都是非负
        for (DirectedEdge e : G.edges())
        {
            if (e.weight() < 0)
            {
                System.err.println("检测到负权重的边存在");
                return false;
            }
        }

        // 验证distTo[v]的值和 edgeTo[v]隐含的信息的一致性
        if (distTo[s] != 0.0 || edgeTo[s] != null)
        {
            System.err.println("distTo[s] 的值与 edgeTo[s] 隐含的信息不一致,s 居然不是起点,算法逻辑矛盾");
            return false;
        }
        for (int v = 0; v < G.V(); v++)
        {
            if (v == s)
            {
                continue;
            }
            if (edgeTo[v] == null && distTo[v] != Double.POSITIVE_INFINITY)
            {
                System.err.println("distTo[v] 的值与 edgeTo[v] 隐含的信息不一致,算法逻辑矛盾");
                return false;
            }
        }

        for (int v = 0; v < G.V(); v++)
        {
            for (DirectedEdge e : G.adj(v))
            {
                int w = e.to();
                if (distTo[v] + e.weight() < distTo[w])
                {
                    System.err.println("边 " + e + " 不是最短边");
                    return false;
                }
            }
        }

        for (int w = 0; w < G.V(); w++)
        {
            if (edgeTo[w] == null)
            {
                continue;
            }
            DirectedEdge e = edgeTo[w];
            int v = e.from();
            if (w != e.to())
            {
                return false;
            }
            if (distTo[v] + e.weight() != distTo[w])
            {
                System.err.println("最短路径上的边 " + e + "居然不是最短边 ");
                return false;
            }
        }
        return true;
    }



    /**
     * 测试
     * @param args
     * @author xxx 2017年3月3日 上午12:28:19
     * @since v1.0
     */
    public static void main(String[] args)
    {
        EdgeWeightedDigraph g = new EdgeWeightedDigraph(5, 9);

        System.out.println(g);

        // 计算最段路径
        DijkstraSP sp = new DijkstraSP(g, 0);

        // 打印出最短路径
        for (int t = 0; t < g.V(); t++)
        {
            if (sp.hasPathTo(t))
            {
                System.out.printf("%d -> %d (%.2f)  ", 0, t, sp.distTo(t));
                for (DirectedEdge e : sp.pathTo(t))
                {
                    System.out.print(e + "   ");
                }
                System.out.println();
            } else
            {
                System.out.printf("顶点 %d 到顶点  %d 的路径不存在\n", 0, t);
            }
        }
    }

}

你可能感兴趣的:(算法)