提高篇:图的最短路径算法和最小生成树算法

Posted 小文学编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了提高篇:图的最短路径算法和最小生成树算法相关的知识,希望对你有一定的参考价值。


Dijkstra 最短路径算法

操作步骤

通过Dijkstra 计算图G中的最短路径时,需要指定起始点vs(即从顶点vs开始计算)。此外,引进两个集合s和u,s的作用是记录已经求出最短路径的顶点,而u则是记录还未求出最短路径的顶点(以及该顶点到起点vs的距离)

(1)初始时,S只包含起点vs,U包含除了vs 外的其他顶点,且u中的顶点的距离为“起点vs到该顶点的距离“【例如:u中顶点v的距离为(vs,v)的长度,然后vs 和v相邻,则v的距离 为 正无穷】

(2)从U中选出“距离最短的顶点k”,并将顶点k加入到s中,同时,从u中移除顶点k

(3)更新u中各个顶点到起点的vs的距离,之所以更新u中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其他顶点的距离。例如,(vs,v)的距离可能大于(vs,k)+(k,v)的距离

(4)重复步骤 2 和 3 ,知道遍历完所有顶点

代码实操

例子:计算下图起始定点到各个顶点的最短路径

//Dijkstra 最短路径算法
public class ShortPathDijkstra {
   //邻接矩阵
   private int[][] matrix;
   //表示正无穷
   private int MAX_LENGTH = Integer.MAX_VALUE;
   //顶点集合
   private String[] vertexes;
   /**
    * 创建图
    * @param index
    */
   public void createGraph(int index){
       matrix = new int[index][index];
       vertexes = new String[index];

       int[] v0 = {0,1,5,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH};
       int[] v1 = {1,0,3,7,5,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH};
       int[] v2 = {5,3,0,MAX_LENGTH,1,7,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH};
       int[] v3 = {MAX_LENGTH,7,MAX_LENGTH,0,2,MAX_LENGTH,3,MAX_LENGTH,MAX_LENGTH};
       int[] v4 = {MAX_LENGTH,5,1,2,0,3,6,9,MAX_LENGTH};
       int[] v5 = {MAX_LENGTH,MAX_LENGTH,7,MAX_LENGTH,3,0,MAX_LENGTH,5,MAX_LENGTH};
       int[] v6 = {MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,3,6,MAX_LENGTH,0,2,7};
       int[] v7 = {MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,9,5,2,0,4};
       int[] v8 = {MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,MAX_LENGTH,7,4,0};

       matrix[0] = v0;
       matrix[1] = v1;
       matrix[2] = v2;
       matrix[3] = v3;
       matrix[4] = v4;
       matrix[5] = v5;
       matrix[6] = v6;
       matrix[7] = v7;
       matrix[8] = v8;

       vertexes[0] = "v0";
       vertexes[1] = "v1";
       vertexes[2] = "v2";
       vertexes[3] = "v3";
       vertexes[4] = "v4";
       vertexes[5] = "v5";
       vertexes[6] = "v6";
       vertexes[7] = "v7";
       vertexes[8] = "v8";
  }

   /**
    * Dijkstra 最短路径算法
    * vs --起始顶点(start vertex),即,统计图中 顶点vs 到其他各个顶点的最短路径
    * @param vs
    */
   public void dijkstra(int vs){
       //flag[i] = true 表示顶点 到 顶点 i 的最短路径已经成功获取
       boolean[] flag = new boolean[vertexes.length];
       //u是 记录 还未求出最短路径的顶点(以及该顶点到起点s的距离,与falg 配合使用,flag[i] == true 表示u中i顶点已被移除)
       int[] u = new int[vertexes.length];
       // 前驱顶点数组,即perv[i] 的值是"顶点vs"到顶点i的最短路径锁经历的全部顶点中,位于顶点i之间的那个顶点
       int[] prev = new int[vertexes.length];
       //s的作用是记录已经求出最短路径的顶点
       String[] s = new String[vertexes.length];

       //步骤1 ,初始时,s中只有起点vs,u中除vs之外的顶点,并且u中顶点的路径是 起点vs 到该顶点的路径
       for(int i=0;i<vertexes.length;i++){
           flag[i] = false;//顶点i 的最短路径还没有获取到
           u[i] = matrix[vs][i];//顶点i 与顶点 vs的初始距离为 顶点 vs 到顶点i的权,也就是 邻接矩阵 vs 行的数据

           prev[i] = 0;//顶点 i 的前驱顶点 为 0
      }

       //将vs 从u 中 移出 (u与flag 配合使用)
       flag[vs] = true;
       u[vs] = 0;
       //将 vs 顶点 加入 s
       u[vs] = 0;
       s[0] = vertexes[vs];
       //步骤1 结束
       
       //步骤四:重复步骤2,3,直到遍历完所有的顶点
       //遍历 vertexes.length -1次,每次找出一个顶点的最短路径
       int k =0;
       for(int i=1;i<vertexes.length;i++) {
           //步骤2:从U中 找出路径最短的顶点,并将其加入到S中(如果vs顶点 到x 顶点还有更短的路径的haul,那么必然会有一个y顶点到vs顶点
           // 的路径币前者更短且没有加入s中,所以u中路径最短顶点的路径就是该顶点的最短路径)
           //即,在未获取最短路径的顶点中,找到离vs最近的顶点k
           int min = MAX_LENGTH;
           for (int j = 0; j < vertexes.length; j++) {
               if (flag[j] == false && u[j] < min) {
                   min = u[j];
                   k = j;
              }
          }
           //将k 加入s中
           s[i] = vertexes[k];
           //步骤2结束

           //步骤三,更新u中的顶点和顶点对应的路径
           //标记 顶点k 为已经获取到最短路径(更新u中的 顶点,即将k 顶点对应的flag 标记为 true)
           flag[k] = true;

           //修正当前 最短路径和前驱顶点(更新u中剩余顶点对应的路径)
           //即,当已经 顶点 k 的最短路径之后 更新 未获取最短路径的顶点的最短路径和前驱顶点
           for (int j = 0; j < vertexes.length; j++) {
               //以k 顶点 所在位置连线其他顶点,判断其他顶点经过最短路径顶点k 到达 vs 顶点是否小于 目前的最短路径,是,更新入u,不是不做处理
               int tmp = (matrix[k][j] == MAX_LENGTH ? MAX_LENGTH : (min + matrix[k][j]));
               if (flag[j] == false && (tmp < u[j])) {
                   u[j] = tmp;
                   //更新j 顶点 的最短路径 前驱顶点为k
                   prev[j] = k;
              }
          }
           //步骤三结束
      }
       //步骤四结束
       //打印Dijkstra 最短路径结束
       System.out.println("起始顶点:"+ vertexes[vs]);
       for(int i=0;i<vertexes.length;i++){
           System.out.print("最短路径("+ vertexes[vs] + "," + vertexes[i] + "):"+ u[i]+ " ");
           List<String> path = new ArrayList<>();
           int j=i;
           while (true){
               path.add(vertexes[j]);
               if(j == 0){
                   break;
              }
               j = prev[j];
          }
           for(int x= path.size() -1 ; x>=0;x--){
               if(x == 0){
                   System.out.println(path.get(x));
              }else{
                   System.out.print(path.get(x) + " -> ");
              }
          }
      }
       System.out.println("顶点放入到s中的顺序");
       for(int i=0; i < vertexes.length;i++){
           System.out.print(s[i]);
           if(i != vertexes.length){
               System.out.print("-->");
          }
      }
  }
   public static void main(String[] args) {
       ShortPathDijkstra shortPathDijkstra = new ShortPathDijkstra();
       shortPathDijkstra.createGraph(9);
       shortPathDijkstra.dijkstra(0);
  }
}

结果:

提高篇:图的最短路径算法和最小生成树算法

最小生成树算法

基本概念

在含有n个顶点的连通图中选择n-1 条边,构成一棵 极小连通子图,并且使连通子图中n-1条边上权值和达到最小,则称其为连通网的最小生成树。

计算的例子

提高篇:图的最短路径算法和最小生成树算法

用邻接表构造图:

private int mEdgNum; //边的数量
private char[] mVexs;//顶点集合
private int[][] mMatrix;//邻接矩阵
private static final int INF = Integer.MAX_VALUE;

public SmallestTree(char[] vexs,int[][] matrix){
   //初始化 顶点是和 边数
   int vlen = vexs.length;

   //初始化 顶点
   mVexs = new char[vlen];
   for(int i=0;i<mVexs.length;i++){
       mVexs[i] = vexs[i];
  }

   //初始化边
   mMatrix = new int[vlen][vlen];
   for(int i=0;i<vlen;i++){
       for(int j=0;j<vlen;j++){
           mMatrix[i][j] = matrix[i][j];
      }
  }
   //统计边
   mEdgNum = 0;
   for(int i=0;i<vlen;i++){
       for(int j = i+1 ; j<vlen ;j++){
           if(mMatrix[i][j] != INF){
               mEdgNum++;
          }
      }
  }
}

/**
* 返回ch的位置
* @param ch
* @return
*/
private int getPosition(char ch){
   for(int i=0;i<mVexs.length ; i++){
       if(mVexs[i] == ch){
           return i;
      }
  }
   return -1;
}

/**
* 打印矩阵队列图
*/
public void print(){
   System.out.printf("Matrix Graph:\n ");
   for (int i=0;i<mVexs.length;i++){
       for(int j=0;j<mVexs.length;j++){
           System.out.printf("%10d",mMatrix[i][j]);
      }
       System.out.println();
  }
}

Prime算法

基本思想:对于图而言,V是所有顶点的集合。现在设置两个新的的集合U和T,其中U用于存放图中最小生成树的顶点,T存放图的最小生成树中的边。从所有  u属于U  v属于(V-U) 【V-U 表示除去U的所有顶点】 的边中选取权值最小的边(u,v),将顶点 v 加入集合 U中,将边(u,v) 加入集合T中,如此不断重复,直到U=V为止,最小生成树构造完毕,这时集合T中包含了最小生成树中的所有边。

/**
* prime 最小生成树算法
* @param start
*/
public void prim(int start){
   int num = mVexs.length; //顶点个数
   int index = 0; //prime 最小树的索引,即prime 数组的索引
   char[] prims = new char[num];//prime 最小树 的结果数组
   int[] weights = new int[num];//顶点间边的权值

   //prime 最小生成树 中第一个数 是 图中第 start个顶点, 因为是从start 开始的
   prims[index++] = mVexs[start];

   //初始化 顶点的权值数组
   //将每个顶点的权值初始化为 第 start 个 顶点 到该顶点的权值
   for (int i=0;i<num ;i++){
       weights[i] = mMatrix[start][i];
  }
   //将第start 个顶点的权值初始化为0
   //可以理解为 第 start 个 顶点到其它自身的距离为0
   weights[start] = 0;

   for (int i=0;i<num ; i++){
       //由于从start 开始 的,因此不需要再对 第start 个 顶点进行处理
       if(start == i){
           continue;
      }
       int j= 0;
       int k  =0;
       int min = INF;
       //在未被加入到最小生成树的顶点中,找出权值最小的顶点
       while (j < num){
           //若 weights[j] = 0 .意味着 第 j 个顶点已经排序过了(或者已经加入到最小生成树中了)
           if(weights[j] != 0 && weights[j] <min){
               min = weights[j];
               k = j;
          }
           j++;
      }

       //经过上面的处理后,在未被加入到最小生成树的顶点,权值最小的顶点是第k个顶点
       //将第k个顶点加入到最小生成树的结果数组中
       prims[index++] = mVexs[k];
       //将 第k个顶点加入到最小生成树的结果数组中
       weights[k] = 0;
       //当第k 个 顶点 被加入到最小生成树 的结果数组中之后,更新其他顶点的权值
       for(j = 0;j<num ;j++){
           //将 第j 个顶点 被加入到 最小生活树的结果数组之中,更新其他顶点的权值
           if(weights[j] != 0 && mMatrix[k][j] < weights[j]){
               weights[j] = mMatrix[k][j];
          }
      }
  }

   //计算最小生活树的权值
   int sum = 0;
   for(int i =1;i<index ; i++){
       int min =INF;
       //获取 prims[i] 在mMatrix 中的位置
       int n = getPosition(prims[i]);
       // 在 vexs[0...i]中 ,找出 到j的权值最小的顶点
       for(int j = 0;j<i;j++){
           int m = getPosition(prims[j]);
           if(mMatrix[m][n] < min){
               min = mMatrix[m][n];
          }
      }
       sum += min;
  }

   //打印最小生成树
   System.out.printf("PRIM(%c) = %d",mVexs[start],sum);
   for(int i =0;i<index;i++){
       System.out.printf("%c",prims[i]);
  }
   System.out.println();
}
//测试
   public static void main(String[] args) {
       char[] vexs = {'A','B','C','D','E','F','G'};
       int matrix[][] = {
              {0,12,INF,INF,INF,16,14},
              {12,0,10,INF,INF,7,INF},
              {INF,10,0,3,5,6,INF},
              {INF,INF,3,0,4,INF,INF},
              {INF,INF,5,4,0,2,8},
              {16,7,6,INF,2,0,9},
              {14,INF,INF,INF,8,9,0},
      };
       SmallestTree smallestTree;

       smallestTree = new SmallestTree(vexs,matrix);

       smallestTree.prim(0);
       smallestTree.kruskal();

  }

结果:

提高篇:图的最短路径算法和最小生成树算法

最小生成树的权重:36

Kruskal算法

基本思想:按照权值从小到大的顺序选择n-1条边,并保证 n-1 条边不构成回路。

具体做法:首先构造一个只含有n个顶点的森林。然后依照权值从小到大从连通图中选择边加入到森林中,并使得森林中不产生回路,直至森林变成一棵树为止。

/**
* kruskal 最小生成树算法
*/
public void kruskal(){
   int index = 0; //rets 数组的索引
   int[] vends = new int[mEdgNum];//用于保存 “已有最小生成树”中每个顶点在该最小树中的终点
   EData[] rets = new EData[mEdgNum]; //结果数组,保存kruskal 最小生成树的边
   EData[] edges;

   //获取图中所有的边
   edges = getEdges();
   //将边按照权的大小进行排序
   edges = sortEdges(edges,mEdgNum);

   for (int i=0; i< mEdgNum ; i++){
       int p1 = getPosition(edges[i].start);//获取 第 i 条边的起点的序号
       int p2 = getPosition(edges[i].end); //获取第i条边的终点的序号

       int  m = getEnd(vends,p1); //获取 p1 在 已有的最小生成树中的终点
       int  n = getEnd(vends,p2);//获取p2 在已有的最小生成树中的终点

       //如果 m != n 意味着 边i 与 已经添加到最小生成树中的顶点没有形成环路
       if(m != n){
           vends[m] = n;//设置 m 在已有的最小生成树 中的终点位n
           rets[index++] = edges[i];// 保存结果
      }
  }

   //统计 并打印 kruskal 最小生成树的信息
   int length = 0;
   for(int i=0;i<index;i++){
       length += rets[i].weight;
  }
   System.out.printf("Kruskal=%d",length);
   for(int i = 0 ;i<index ; i++){
       System.out.printf("(%c,%c)",rets[i].start,rets[i].end);
  }
   System.out.println();
}

private static class EData{
   char start;
   char end;
   int weight;

   public EData(char start,char end,int weight){
     this.start = start;
     this.end = end;
     this.weight = weight;
  }
}

/**
* 获取i的终点
* @param vends
* @param i
* @return
*/
private int getEnd(int[] vends,int i){
   while (vends[i] != 0){
       i = vends[i];
  }
   return i;
}

private EData[] sortEdges(EData[] edges,int elen){
   for(int i=0;i<elen;i++){
       for (int j=i+1; j<elen;j++){
           if(edges[i].weight > edges[j].weight){
               EData temp = edges[i];
               edges[i] = edges[j];
               edges[j] = temp;
          }
      }
  }
   return edges;
}

private EData[] getEdges(){
   int index = 0;
   EData[] edges;

   edges = new EData[mEdgNum];
   for(int i=0;i< mVexs.length ;i++){
       for(int j=i+1;j <mVexs.length;j++){
           if(mMatrix[i][j] != INF){
               edges[index++] = new EData(mVexs[i],mVexs[j],mMatrix[i][j]);
          }
      }
  }
   return edges;
}

结果:

最小生成树的权重为:36敬请关注!


以上是关于提高篇:图的最短路径算法和最小生成树算法的主要内容,如果未能解决你的问题,请参考以下文章

如何依次找到无向图的 前k k 条 最短路径

图论学习

图中基于点的两大算法总结

最小生成树 prime kruskal

详解图(性质,结构,遍历,最小生成树,最短路径)

详解图(性质,结构,遍历,最小生成树,最短路径)