zoukankan      html  css  js  c++  java
  • 数据结构(十):复杂图-加权有向图,最短路径

     

    一、 加权有向图概述

      加权有向图是在加权无向图的基础上,给边添加了方向,并且一条加权有向边只会在一个顶点的邻接表中出现。

    二、 加权有向图实现

      为了体现边的有向性,我们需要知道边的起点和终点,参照如下来构建有向边,而有向图的构建只需在前面无向图的基础上,将无向边对象更换为有向边对象即可

    /**

     * 有向边对象

     * @author jiyukai

     */

    public class DirectedEdge {

           //有向边起点

           private int v;

          

           //有向边终点

           private int w;

          

           //有向边权重

           private double weight;

           public DirectedEdge(int v, int w, double weight) {

                  super();

                  this.v = v;

                  this.w = w;

                  this.weight = weight;

           }

          

           /**

            * 起点

            * @return

            */

           public int from() {

                  return v;

           }

          

           /**

            * 终点

            * @return

            */

           public int to() {

                  return w;

           }

          

           /**

            * 有向边权重

            * @return

            */

           public Double weight() {

                  return weight;

           }

    }

    import com.data.struct.common.list.queue.Queue;

    /**

     * 有向图实现

     * @author jiyukai

     */

    public class Digraph {

           //定点个数

           public int V;

          

           //边的数量

           public int E;

          

           //图的邻接表

           public Queue<DirectedEdge>[] qTable;

          

           public Digraph(int v) {

                  this.V = v;

                 

                  this.E = 0;

                 

                  //初始化邻接表,数组中的索引为顶点,值为已队列,存放相邻的顶点

                  qTable = new Queue[v];

                  for(int i=0;i<v;i++) {

                         qTable[i] = new Queue<DirectedEdge>();

                  }

           }

          

           /**

            * 向图中添加一条有向边

            * @param v

            * @param w

            */

           public void addEdge(DirectedEdge e) {

                 

                  //获取边的起点

                  int v = e.from();

                 

                  //起点到终点的指向

                  qTable[v].enqueue(e);

                 

                  //边加1

                  E++;

           }

          

           /**

            * 返回当前顶点的数量

            * @return

            */

           public int V() {

                  return V;

           }

          

           /**

            * 返回当前边的数量

            * @return

            */

           public int E() {

                  return E;

           }

          

           /**

            * 获取与顶点V相邻的顶点

            * @param V

            * @return

            */

           public Queue<DirectedEdge> adjoin(int V) {

                  return qTable[V];

           }

          

           /**

            * 获取加权有向图的所有边

            * @return

            */

           public Queue<DirectedEdge> directEdges(){

                  Queue<DirectedEdge> diedges = new Queue<DirectedEdge>();

                 

                  //由于有向图中,有向边只存在一个顶点的邻接表中,不存在相同边出现在不同顶点的邻接表中的情况,所以遍历添加即可

                  for(int v=0;v<V;v++) {

                         for(DirectedEdge e : qTable[v]) {

                                diedges.enqueue(e);

                         }

                  }

                 

                  return diedges;

           }

    }

    三、 最短路径

      最短路径用来在加权有向图中,寻找顶点v到顶点w经过的有向边的最短路径,如下为一幅加权的有向图,各边的指向和权限已在图和表格中标出,红色边即为顶点0到顶点4经过的最短路径。

           

    四、 松弛思想和Disjstra实现思路

      那么如何在一副有向图中求得最短路径呢,这里我们用到了一种思想叫松弛思想,如下图

      同样是顶点0到顶点2的路径,图一的蓝色边框看做皮筋,那么皮筋圈住的距离是0.35+0.15=0.5,图二的蓝色边框看做皮筋,那么皮筋圈住的距离是0.04,同样是起点为0,终点为2

      我们把图一的皮筋改成图二后,皮筋很明显的松弛了,即在松弛状态下达到了和图一一样的效果。

       

      这种松弛的原理和方法可以用来解决最短路径的问题,我们在算最短路径时,需要有个数组edges[]存放当前顶点和上一个顶点的最短边,索引为顶点,值为边

      edgesWeight[]存放起点s到其他顶点的权重之和,还有一个索引优先队列minQueue存放最短路径到各个顶点的有效最短边。

      松弛:

      原路径顶点0到顶点3放松到边4-3,意味着需要判断顶点0到顶点3的最短路径是否从0-4,再从4-3,从如下图来看若放松到4-3,则最小路径变成了7.2+1.1=8.3>4.5,所以这时忽略放到4-3的请求。

       

      从如下图来看若放松到4-3,则最小路径变成了0.6+1.1=1.7<4.5,所以这时需要将4-3添加到edges[3]的最小路径边中,同时更改edgesWeight[3]=1.7

       

      如下通过一个简单的过程来演示找到一副图中的最短路径,初始化状态如下

                 

      步骤一:遍历起点0的邻接边,找到最短的边0-1添加到最短路径中,同时加入到edges数组中,并将0到1的权重之和添加到edgesWeight[]数组中,修改顶点0到各个顶点的有效横切边minQueue。

               

      步骤二:遍历0和1组成的最短路径的邻接边,通过比较0-2的距离0.35>0-1,1-2的距离0.19,找到最短路径1-2加入到最短路径中,并同时修改edges[]数组和edgesWeight数组

      修改顶点0和1组成的最短路径到各个顶点的有效横切边minQueue。

     

             

      步骤三:找起点v到w的最短路径时,只需从edges数组逆向查找即可,比如查找0-2的最短路径,首先获取edges[2],然后通过边对象取到2的起点1,找到edge[1],依次找到起点0

      则可以发现最短路径为0-1,1-2

    五、 Disjstra代码实现

    import com.data.struct.common.list.queue.Queue;

    import com.data.struct.common.tree.priority.queue.IndexMinPriorityQueue;

    /**

     * Dijkstra算法实现

     * @author jiyukai

     */

    public class DijkstraSP {

           // 存放当前顶点和上一个顶点的最短边,索引为顶点,值为边

           private DirectedEdge[] edges;

           // 存放起点s到其他顶点的权重之和

           private double[] edgesWeight;

           // 存放最小生成树顶点与非最小生成树顶点的目标横切边,索引为顶点

           private IndexMinPriorityQueue<Double> minQueue;

           public DijkstraSP(Digraph G, int s) throws Exception {

                  edges = new DirectedEdge[G.V()];

                  edgesWeight = new double[G.V()];

                 

                  // 初始化时将edgesWeight的值暂时设置为无穷大,起点处设置为0

                  for (int i = 0; i < edgesWeight.length; i++) {

                         edgesWeight[i] = Double.POSITIVE_INFINITY;

                  }

                  edgesWeight[0] = 0.0;

                  minQueue = new IndexMinPriorityQueue<>(G.V());

                  minQueue.insert(0, 0.0);

                  while (!minQueue.isEmpty()) {

                         // 松弛图G中的顶点

                         relax(G, minQueue.delMin());

                  }

           }

           /**

            * 进行松弛操作

            * @param g

            * @param delMin

            * @throws Exception

            */

           private void relax(Digraph G, int v) throws Exception {

                  // 首先遍历顶点的邻接边

                  for (DirectedEdge e : G.qTable[v]) {

                         // 获取边的终点

                         int w = e.to();

                         // 起点到顶点w的权重是否大于起点到顶点v的权重+边e的权重

                         // 如果大于,则修改s->w的路径,edges[w]=e,并修改edgesWeight[v] =

                         // edgesWeight[v]+e.weight(),如果不大于,则忽略

                         if (edgesWeight[v] + e.weight() < edgesWeight[w]) {

                                edges[w] = e;

                                edgesWeight[w] = edgesWeight[w] + e.weight();

                                // 如果顶点w已经存在于优先队列minQueue中,则重置顶点w的权重

                                if (minQueue.contains(w)) {

                                       minQueue.changeItem(w, edgesWeight[w]);

                                } else {

                                       // 如果顶点w没有出现在优先队列pq中,则把顶点w及其权重加入到pq中

                                       minQueue.insert(w, edgesWeight[w]);

                                }

                         }

                  }

           }

           /**

            * 判断从顶点s到顶点v是否可达

            * @param v

            * @return

            */

           public boolean hasPathTo(int v) {

                  return edgesWeight[v] < Double.POSITIVE_INFINITY;

           }

           /**

            * 逆向查询从起点s到顶点v的最短路径中所有的边

            * @param v

            * @return

            */

           public Queue<DirectedEdge> pathTo(int v){

                  Queue<DirectedEdge> minPaths = new Queue<>();

                  //不存在连通的路径,则返回null

                  if(!hasPathTo(v)) {

                         return null;

                  }

                 

                  //逆向查找过程

                  DirectedEdge e = null;

                 

                  while(true){

                         e = edges[v];

                         if(e==null) {

                                break;

                         }

                        

                         minPaths.enqueue(e);

                         v = e.from();

                  }

                 

                  return minPaths;

           }

    }

  • 相关阅读:

    二分查找法
    LeetCode-Two Sum III
    LeetCode-Add and Search Word
    LeetCode-Longest Substring with At Least K Repeating Characters
    LeetCode-Rearrange String k Distance Apart
    LeetCode-Game of Life
    LeetCode-Walls and Gates
    LeetCode-Water and Jug Problem
    LeetCode-Inorder Successor in BST
  • 原文地址:https://www.cnblogs.com/jiyukai/p/14090655.html
Copyright © 2011-2022 走看看