克鲁斯卡尔算法_修路问题

克鲁斯卡尔算法

问题描述

修路问题:七个村庄(A,B,C,D,E,F,G)要修路,七个村庄之间路线很多,要怎样才可以在保证联通所有村庄的同时修最短的路

在这里插入图片描述

思路分析

我们可以把所有的边存入数组,然后按照从小到大的顺序取出2构建最小树,但是要保证取出的边不会构成回路。如图我们有十条边,先都存入数组,然后取出依次取出,先取出最短的F->E,不构成回路,继续取C->D,依然图中不构成回路,继续取D->E,现在可以看见F->C这条线是联通的了,然后继续取最短,这时可以发现C->F是最短的,但是很明显的看到如果加入了C->F这条路就构成了回路。那我们是不是可以理解为按照从小到大的顺序去取出,只要不构成回路问题就解决了。

回路问题思路(重点理解)

如果我们有个数组动态记录每次取出后这些点的终点,那我们是不是就可以解决问题。最开始的时候每个点的终点就是自己,当第一次取出(F->E)这条边后,F的终点还是自己,但是E的终点就是F了,然后继续取,取出C->D,C的终点就是D,D的终点还是D,继续取D->E,当这条边取出来的时候D的终点就是跟E一样了(D的终点引用E的,E是F) ,因为已经串起来成为一条线了,那么此时(C,D,E,F)的终点都是F,继续取C->F,此时这条边加入的时候C指向F,F指向F。这时就构成回路了。简而言之,我们加入的边的两个顶点不能都指向同一个终点,否则将构成回路。

问题解决思路

这个时候我们要做的就是两件事。首先把所有的边存起来,然后排序,保证从小到大的顺序。第二步就是取出来每条边的同时判断是否构成回路。当边全部取完时,问题解决。

代码实现

public class Kruskal {
private int edgeNum;//边的个数
private char[] vertexs;//顶点数组
private int[][] matrix; // 邻接矩阵
private static final int INF = Integer.MAX_VALUE;

public static void main(String[] args) {
char[] vertexs = {'A', 'B', 'C', 'D', 'E'
, 'F', 'G'};

int[][] matrix = {
        /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
        /*A*/ {0, 12, INF, INF, INF, 16, 14},
        /*B*/ {12, 0, 10, INF, INF, 7, INF},
        /*C*/ {INF, 10, 0, 3, 5, 6, INF},
        /*D*/ {INF, INF, 3, 0, 4, INF, INF},
        /*E*/ {INF, INF, 5, 4, 0, 2, 8},
        /*F*/ {16, 7, 6, INF, 2, 0, 9},
        /*G*/ {14, INF, INF, INF, 8, 9, 0}};
Kruskal kruskal = new Kruskal(vertexs, matrix);
kruskal.print();

  kruskal.kruskal();

}

/**
* 定义构造方法初始化数据
* @param vertexs 顶点数组
* @param matrix  领接矩阵
*/
public Kruskal(char[] vertexs, int[][] matrix) {
  int vlen = vertexs.length;

  this.vertexs = new char[vlen];
  this.matrix = new int[vlen][vlen];

  // 初始化顶点
  for (int i = 0; i < vlen; i++) {
      this.vertexs[i] = vertexs[i];
  }

  // 初始化二维数组
  for (int i = 0; i < vlen; i++) {
      for (int j = 0; j < vlen; j++) {
          this.matrix[i][j] = matrix[i][j];
      }
  }

  // 计算有多少条边
  for (int i = 0; i < vlen; i++) {
      for (int j = i + 1; j < vlen; j++) {
          if (this.matrix[i][j] != INF) {
              edgeNum++;
          }
      }
  }
}

public void kruskal(){
  // 结果数组的索引
  int index = 0;
  // 用于保存已生成的最小数中的每个顶点在最小生成树中的终点
  int[] ends = new int[edgeNum];

  // 创建结果数组 保存最后的最小生成树
  EData[] rets = new EData[edgeNum];

  // 获取图中所有的边集合
  EData[] edges = getEdges();

  sortEdges(edges);

  // 遍历数组将边添加到最小生成树是 判断是否形成回路
  for (int i = 0; i < edgeNum; i++) {
      int p1 = getPosition(edges[i].start);
      int p2 = getPosition(edges[i].end);

      // 获取p1顶点在这个最小生成树中的终点
      int m = getEnd(ends,p1);
      int n = getEnd(ends,p2);

      // 判断是否构成回路
      if (m != n){
          ends[m] = n; // 设置m在已有最小生成树中的终点
          rets[index++] = edges[i];
      }
  }

  System.out.println("最小生成树");
  for (int i = 0; i < index; i++) {
      System.out.println(rets[i]);
  }
}





public void print() {
    System.out.println("领接矩阵为");
    for (int i = 0; i < vertexs.length; i++) {
      for (int j = 0; j < vertexs.length; j++) {
         System.out.printf("%13d", matrix[i][j]);
      }
       System.out.println();
    }
}


// 我们需要对边的权值进行排序
private void sortEdges(EData[] edges) {
 for (int i = 0; i < edges.length - 1; i++) {
   for (int j = 0; j < edges.length - 1 - i; j++) {
      if (edges[j].weight > edges[j + 1].weight) {
           EData temp = edges[j];
           edges[j] = edges[j + 1];
           edges[j + 1] = temp;
       }
     }
 }
}

// 返回顶点对应的下标
private int getPosition(char ch) {
 for (int i = 0; i < vertexs.length; i++) {
     if (vertexs[i] == ch) {
         return i;
     }
 }
 return -1;
}

// 获取图中的边 放入数组中
private EData[] getEdges() {
 int index = 0;
 EData[] edgs = new EData[edgeNum];
 for (int i = 0; i < vertexs.length; i++) {
     for (int j = i + 1; j < vertexs.length; j++) {
         if (matrix[i][j] != INF) {
             edgs[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
         }
     }
 }
 return edgs;
}


/**
 *     获取下标为i的顶点的终点 用于后面判断两个顶点的
 * 终点是否相同
 * @param ends  数组就是记录各个顶点对应的终点是哪个
 *  ends数组是在遍历的过程中 逐步形成的
 * @param i  传入顶点的下标
 * @return 返回的是下标为i的这个顶点对应终点的下标
 */
 private int getEnd(int[] ends,int i){
     while (ends[i] != 0){
         i = ends[i];
     }
     return i;
 }


}

/**
 * 创建一个类EData,它的对象实例就表示一条边
 */
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;
 }

 @Override
 public String toString() {
     return "EData{<" +start +
             ", " + end +
             ">=" + weight +
             '}';
 }
}

运行结果
在这里插入图片描述

总结

相比起普利姆算法点击传送,克鲁斯卡尔相对适合求边稀疏的网的最小生成树。在这里,克鲁斯卡尔算法更多的是去考虑是否构成回路这个问题,如果这里想通了,应该就很容易想通了。

秃头萌新一枚 多多关照

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值