zoukankan      html  css  js  c++  java
  • 加权有向图和最短路径

    加权有向图

    之前学习的加权无向图中,边是没有方向的,并且同一条边会同时出现在该边的两个顶点的邻接表中,为了能够处 理含有方向性的图的问题,我们需要实现以下加权有向图。

    加权有向图边的表示

    类名 DirectedEdge
    构造方法 DirectedEdge(int v,int w,double weight):通过顶点v和w,以及权重weight值构造一个边对象
    成员方法 1.public double weight():获取边的权重值
    2.public int from():获取有向边的起点
    3.public int to():获取有向边的终点
    成员变量 1.private final int v:起点
    2.private final int w:终点
    3.private final double weight:当前边的权重

    代码:

    /**
     * 有向边
     * @author wen.jie
     * @date 2021/8/30 14:39
     */
    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 this.weight;
        }
    
        //起点
        public int from() {
            return v;
        }
    
        //终点
        public int to() {
            return w;
        }
        
        @Override
        public String toString() {
            return "DirectedEdge{" +
                    "v=" + v +
                    ", w=" + w +
                    ", weight=" + weight +
                    '}';
        }
    }
    

    加权有向图的实现

    类名 EdgeWeightedDigraph
    构造方法 EdgeWeightedDigraph(int V):创建一个含有V个顶点的空加权有向图
    成员方法 1.public int V():获取图中顶点的数量
    2.public int E():获取图中边的数量
    3.public void addEdge(DirectedEdge e):向加权有向图中添加一条边e
    4.public Queue adj(int v):获取由顶点v指出的所有的边
    5.public Queue edges():获取加权有向图的所有边
    成员变量 1.private final int V: 记录顶点数量
    .private int E: 记录边数量
    3.private Queue[] adj: 邻接表

    代码:

    /**
     * 加权有向图
     * @author wen.jie
     * @date 2021/8/30 14:42
     */
    public class EdgeWeightedDigraph{
    
        private final int V;
    
        private int E;
    
        private Queue<DirectedEdge>[] adj;
    
        public EdgeWeightedDigraph(int V) {
            this.V = V;
            this.E = E;
    
            this.adj = new Queue[V];
            for (int i = 0; i < adj.length; i++) {
                adj[i] = new Queue<>();
            }
        }
    
        public int V() {
            return V;
        }
    
        public int E() {
            return E;
        }
    
        //向加权有向图中添加一条边
        public void addEdge(DirectedEdge e) {
            int v = e.from();
            adj[v].enqueue(e);
            E++;
        }
    
        //获取由顶点v指出的所有的边
        public Queue<DirectedEdge> adj(int v) {
            return adj[v];
        }
    
        //获取加权有向图中的所有边
        public Queue<DirectedEdge> edges() {
            Queue<DirectedEdge> allEdges = new Queue<>();
            for (int v = 0; v < V; v++) {
                for (DirectedEdge edge : adj[v]) {
                    allEdges.enqueue(edge);
                }
            }
            return null;
        }
    }
    

    最短路径

    最短路径定义及性质

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

    image-20210830145351225

    性质:

    1.路径具有方向性;

    2.权重不一定等价于距离。权重可以是距离、时间、花费等内容,权重最小指的是成本最低

    3.只考虑连通图。一副图中并不是所有的顶点都是可达的,如果s和t不可达,那么它们之间也就不存在最短路径,为了简化问题,这里只考虑连通图。

    4.最短路径不一定是唯一的。从一个顶点到达另外一个顶点的权重最小的路径可能会有很多条,这里只需要找出一条即可。

    最短路径树:给定一副加权有向图和一个顶点s,以s为起点的一棵最短路径树是图的一副子图,它包含顶点s以及从s可达的所有顶点。这棵有向树的根结点为s,树的每条路径都是有向图中的一条最短路径。

    最短路径树API设计

    计算最短路径树的经典算法是dijstra算法,为了实现它,先设计如下API:

    类名 DijkstraSP
    构造方法 public DijkstraSP(EdgeWeightedDigraph G, int s):根据一副加权有向图G和顶点s,创建一个计算顶点为s的最短路径树对象
    成员方法 1.private void relax(EdgeWeightedDigraph G, int v):松弛图G中的顶点v
    2.public double distTo(int v):获取从顶点s到顶点v的最短路径的总权重
    3.public boolean hasPathTo(int v):判断从顶点s到顶点v是否可达
    4.public Queue pathTo(int v):查询从起点s到顶点v的最短路径中所有的边
    成员变量 1.private DirectedEdge[] edgeTo: 索引代表顶点,值表示从顶点s到当前顶点的最短路径上的最后一条边
    2.private double[] distTo: 索引代表顶点,值从顶点s到当前顶点的最短路径的总权重
    3.private IndexMinPriorityQueue<Double> pq:存放树中顶点与非树中顶点之间的有效横切边

    松弛技术

    松弛这个词来源于生活:一条橡皮筋沿着两个顶点的某条路径紧紧展开,如果这两个顶点之间的路径不止一条,还有存在更短的路径,那么把皮筋转移到更短的路径上,皮筋就可以放松了。

    image-20210830150451451

    松弛这种简单的原理刚好可以用来计算最短路径树。

    在我们的API中,需要用到两个成员变量edgeTo和distTo,分别存储边和权重。一开始给定一幅图G和顶点s,我们只知道图的边以及这些边的权重,其他的一无所知,此时初始化顶点s到顶点s的最短路径的总权重disto[s]=0;顶点s到其他顶点的总权重默认为无穷大,随着算法的执行,不断的使用松弛技术处理图的边和顶点,并按一定的条件更新edgeTo和distTo中的数据,最终就可以得到最短路径树。

    边的松弛:

    放松边v->w意味着检查从s到w的最短路径是否先从s到v,然后再从v到w?

    如果是,则v-w这条边需要加入到最短路径树中,更新edgeTo和distTo中的内容:edgeTo[w]=表示v->w这条边的DirectedEdge对象,distTo[w]=distTo[v]+v->w这条边的权重;

    如果不是,则忽略v->w这条边。

    image-20210830150636412

    顶点的松弛:

    顶点的松弛是基于边的松弛完成的,只需要把某个顶点指出的所有边松弛,那么该顶点就松弛完毕。例如要松弛顶点v,只需要遍历v的邻接表,把每一条边都松弛,那么顶点v就松弛了。

    如果把起点设置为顶点0,那么找出起点0到顶点6的最短路径0->2->7>3->6的过程如下:

    image-20210830151044608

    Dijstra算法实现

    Disjstra算法的实现和Prim算法很类似,构造最短路径树的每一步都是向这棵树中添加一条新的边,而这条新的边是有效横切边pq队列中的权重最小的边。

    代码:

    /**
     * Dijkstra算法
     * @author wen.jie
     * @date 2021/8/30 15:14
     */
    public class DijkstraSP {
    
        //索引代表顶点,值表示从顶点s到当前顶点的最短路径上的最后一条边
        private DirectedEdge[] edgeTo;
    
        //索引代表顶点,值从顶点s到当前顶点的最短路径的总权重
        private double[] distTo;
    
        //存放树中顶点与非树中顶点之间的有效横切边
        private IndexMinPriorityQueue<Double> pq;
    
        public DijkstraSP(EdgeWeightedDigraph G, int s){
            this.edgeTo = new DirectedEdge[G.V()];
            this.distTo = new double[G.V()];
            Arrays.fill(distTo, Double.POSITIVE_INFINITY);
            pq = new IndexMinPriorityQueue<>(G.V());
    
            distTo[s] = 0.0;
            pq.insert(s, 0.0);
            while (!pq.isEmpty()) {
                relax(G, pq.delMin());
            }
        }
    
        //松弛图G中的顶点v
        private void relax(EdgeWeightedDigraph G, int v){
            for (DirectedEdge edge : G.adj(v)) {
                int w = edge.to();
                //松弛技术
                if (distTo(v) + edge.weight() < distTo(w)){
                    distTo[w] = distTo[v] + edge.weight();
                    edgeTo[w] = edge;
                    if (pq.contains(w)) {
                        pq.changeItem(w, distTo(w));
                    } else {
                        pq.insert(w, distTo(w));
                    }
                }
            }
        }
    
        //获取从顶点s到顶点v的最短路径的总权重
        public double distTo(int v){
            return distTo[v];
        }
    
        //判断从顶点s到顶点v是否可达
        public boolean hasPathTo(int v){
            return distTo[v] < Double.POSITIVE_INFINITY;
        }
    
        //查询从起点s到顶点v的最短路径中所有的边
        public Queue<DirectedEdge> pathTo(int v){
            if (!hasPathTo(v)){
                return null;
            }
            Queue<DirectedEdge> allEdges = new Queue<>();
            while (true) {
                DirectedEdge e = edgeTo[v];
                if (e == null)
                    break;
                allEdges.enqueue(e);
                v = e.from();
            }
            return allEdges;
        }
    
    }
    

    测试:

        String[] strs = new String[]{
                "4 5 0.35",
                "5 4 0.35",
                "4 7 0.37",
                "5 7 0.28",
                "7 5 0.28",
                "5 1 0.32",
                "0 4 0.38",
                "0 2 0.26",
                "7 3 0.39",
                "1 3 0.29",
                "2 7 0.34",
                "6 2 0.40",
                "3 6 0.52",
                "6 0 0.58",
                "6 4 0.93"
        };
    
        @Test
        public void test() {
            EdgeWeightedDigraph G = new EdgeWeightedDigraph(8);
            for (String str : strs) {
                String[] split = str.split(" ");
                DirectedEdge e = new DirectedEdge(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Double.parseDouble(split[2]));
                G.addEdge(e);
            }
    
            DijkstraSP dijkstraSP = new DijkstraSP(G, 0);
            Queue<DirectedEdge> edges = dijkstraSP.pathTo(6);
            for (DirectedEdge edge : edges) {
                System.out.println(edge);
            }
        }
    

    image-20210830154753942

    本文所有代码均已上传:https://gitee.com/wj204811/algorithm

  • 相关阅读:
    The server must be started under an unprivileged user ID to prevent
    it starts (“forks”) a new process for each connection.
    kong
    flume采集微信小程序数据
    bc
    LINUX上一个命令计算PI
    parameters arguments 形式参数 实际参数
    极限编程 简单设计
    Communicating sequential processes CSP 通信顺序进程 CSP writing to a file by name (process, Erlang) vs. writing to a file descriptor (channel, Go)
    hcitool
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/15206314.html
Copyright © 2011-2022 走看看