• 1. 克鲁斯卡尔(Kruskal)算法的原理

    • 1.1. 算法应用场景-公交站问题
    • 1.2. 算法基本介绍
    • 1.3. 算法图解说明
      • 1.3.1. 最小连通子图的概念说明
      • 1.3.2. 构建最小连通子图的步骤
      • 1.3.3. 算法的关键步骤分析
      • 1.3.4. 对回路的概念和判断的说明
  • 2. 克鲁斯卡尔(Kruskal)算法的实现
    • 2.1. 边类
    • 2.2. 算法类
    • 2.3. 测试结果

博主的 Github 地址


1. 克鲁斯卡尔(Kruskal)算法的原理

1.1. 算法应用场景-公交站问题

  • 某市新增 7 个站点 {'A','B','C','D','E','F','G'}, 现要把 7 个站点连通.
  • 各个站点的距离用边线表示(权), 比如 A-B 距离 12 公里.
  • 如何修路保证各个站点都能连通, 并且修建的公路总里程最短?
  • 本质上依旧是最小生成树问题.

1.2. 算法基本介绍

  • 克鲁斯卡尔算法, 是用来求加权连通图的最小生成树的算法.

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

  • 具体做法:
    首先构造一个只含 n 个顶点的森林,
    根据权值从小到大从连通网中选择边加入到森林中,
    并使森灵中不产生回路, 直至森林变成一棵树为止.

1.3. 算法图解说明

1.3.1. 最小连通子图的概念说明

  • 在含有 n 个顶点的连通图中选择 n-1 条边, 构成极小连通子图,
    并使该连通子图中 n-1 条边上的权值之和最小, 称为最小生成树.

  • 如上图所示的连通网可以有多棵权值总和不相同的生成树.

1.3.2. 构建最小连通子图的步骤

  • 以上图为例, 来对克鲁斯卡尔进行演示(假设, 用数组 R 保存最小生成树结果).

  • 第1步: 将边 <E,F> 加入 R 中.
    边 <E,F> 的权值最小, 因此将它加入到最小生成树结果R中.

  • 第2步: 将边 <C,D> 加入 R 中.
    上一步操作之后, 边 <C,D> 的权值最小, 因此将它加入到最小生成树结果 R 中.

  • 第3步: 将边 <D,E> 加入 R 中.
    上一步操作之后, 边<D,E>的权值最小, 因此将它加入到最小生成树结果R中.

  • 第4步: 将边<B,F>加入R中.
    上一步操作之后, 边<C,E>的权值最小, 但<C,E>会和已有的边构成回路;
    因此, 跳过边<C,E>. 同理, 跳过边<C,F>.
    将边<B,F>加入到最小生成树结果R中.

  • 第5步: 将边<E,G>加入R中.
    上一步操作之后, 边<E,G>的权值最小, 因此将它加入到最小生成树结果R中.

  • 第6步: 将边<A,B>加入R中.
    上一步操作之后, 边<F,G>的权值最小, 但<F,G>会和已有的边构成回路;
    因此, 跳过边<F,G>. 同理,跳过边<B,C>.
    将边<A,B>加入到最小生成树结果R中.

  • 此时, 最小生成树构造完成.
    它包括的边依次是: <E,F>, <C,D>, <D,E>, <B,F>, <E,G>, <A,B>.

1.3.3. 算法的关键步骤分析

  • 根据前面介绍的克鲁斯卡尔算法的基本思想和做法,
    可知克鲁斯卡尔算法重点需要解决的以下两个问题:

    • 问题一: 对图的所有边按照权值大小进行由小到大的排序.
    • 问题二: 将边添加到最小生成树中时, 如何判断是否形成回路.
  • 问题一处理方式:

    • 采用排序算法进行排序即可.
  • 问题二处理方式:

    • 记录顶点在最小生成树中的终点,
      顶点的终点是在最小生成树中与它连通的最大顶点.
    • 然后每次需要将一条边添加到最小生存树时,
      判断该边的两个顶点的终点是否重合, 重合则会构成回路.

1.3.4. 对回路的概念和判断的说明

  • 将 <E,F> <C,D> <D,E> 加入到最小生成树后, 这几条边的顶点就有了终点:

    • C的终点是F
    • D的终点是F
    • E的终点是F
    • F的终点是F
  • 终点就是将所有顶点按照从小到大的顺序排列好之后;
    这里按照 char 值进行排序, 因此这几个点最大的是 F.
    所以某个顶点的终点就是与它连通的最大顶点.

  • 因此, 接下来要添加的下一条边的选择中:
    虽然 <C,E> 是权值最小的边, 但是 C 和 E 的终点都是 F, 即它们的终点相同,
    因此, 将 <C,E> 加入最小生成树的话, 会形成回路. 这就是判断回路的方式.

  • 判断回路的方式就是:
    在加入的边中的两个顶点, 它们不能指向同一个终点, 否则会构成回路.

2. 克鲁斯卡尔(Kruskal)算法的实现

  • 实现细节在注释中

2.1. 边类

package com.leo9.dc38.kruskal_algorithm;//创建一个边的类, 它的对象实例就表示一条边
public class SideData {//定义边的两个端点char start_point;char end_point;//定义边的权值int side_weight;public SideData(char start_point, char end_point, int side_weight) {this.start_point = start_point;this.end_point = end_point;this.side_weight = side_weight;}@Overridepublic String toString() {return "<" + start_point +", " + end_point +"> = " + side_weight;}
}

2.2. 算法类

package com.leo9.dc38.kruskal_algorithm;import java.util.Arrays;public class KruskalAlgorithm {//定义边的数量private int side_num;//定义顶点值的数组private char[] vertex_data;//定义邻接矩阵private int[][] graph_matrix;//使用Integer的最大值来表示两点不连通, 即类似无穷远距离private static final int INF = Integer.MAX_VALUE;public static void main(String[] args) {char[] vertex_data = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};int[][] graph_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}};KruskalAlgorithm algorithm_case = new KruskalAlgorithm(vertex_data, graph_matrix);algorithm_case.showMatrix();SideData[] mst_res = algorithm_case.getMST();System.out.println("\n============show the MST=============");showSide(mst_res);}//定义构造函数public KruskalAlgorithm(char[] vertex_data, int[][] graph_matrix) {//定义变量获取顶点数int vertex_num = vertex_data.length;//初始化顶点, 采用值传递的方式this.vertex_data = new char[vertex_num];for (int i = 0; i < vertex_num; i++) {this.vertex_data[i] = vertex_data[i];}//初始化边, 依然采用值传递方式this.graph_matrix = new int[vertex_num][vertex_num];for (int i = 0; i < vertex_num; i++) {for (int j = 0; j < vertex_num; j++) {this.graph_matrix[i][j] = graph_matrix[i][j];}}//统计边的数量, 因为是无向图, 获取一半的边就可以了for (int i = 0; i < vertex_num; i++) {for (int j = i + 1; j < vertex_num; j++) {if (graph_matrix[i][j] != INF) {side_num++;}}}}//定义显示矩阵的方法public void showMatrix() {System.out.println("\n==========show matrix===========");for (int i = 0; i < 7; i++) {if (i == 0) {System.out.printf("%6c    ", 'A' + i);} else {System.out.printf("%-5c", 'A' + i);}}System.out.println();int index = 0;for (int[] row : graph_matrix) {System.out.printf("%-5c", 'A' + index);index++;for (int data : row) {if (data == INF) {System.out.printf("%-5s", "INF");} else {System.out.printf("%-5d", data);}}System.out.println();}}//定义方法对边进行排序, 传入的是边的集合private static void sortSide(SideData[] sides) {for (int i = 0; i < sides.length; i++) {for (int j = 0; j < sides.length - 1 - i; j++) {if (sides[j].side_weight > sides[j + 1].side_weight) {SideData tmp = sides[j];sides[j] = sides[j + 1];sides[j + 1] = tmp;}}}}private static void showSide(SideData[] sides) {int count = 0;for (int i = 0; i < sides.length; i++) {System.out.printf("%-15s", sides[i]);count++;if (count == 3) {System.out.println();count = 0;}}}//定义方法返回传入顶点的下标, 找到则返回数组下标, 否则返回-1.private int getPosition(char vertex) {for (int i = 0; i < vertex_data.length; i++) {if (vertex_data[i] == vertex) {return i;}}return -1;}//定义方法获取图中的边, 放到边数组当中, 后续需要遍历该数组private SideData[] getSides() {//定义一个索引来给边数组遍历时进行使用int index = 0;//定义边数组SideData[] sides = new SideData[side_num];//循环遍历邻接矩阵, 获取边, 也是获取一半的就可以了for (int i = 0; i < vertex_data.length; i++) {for (int j = i + 1; j < vertex_data.length; j++) {if (graph_matrix[i][j] != INF) {sides[index++] = new SideData(vertex_data[i], vertex_data[j], graph_matrix[i][j]);}}}return sides;}//定义方法获取下标为 i 的顶点的终点//用于后面进行判断两个顶点的终点是否相同而形成回路//ends[]数组是记录下标为 i 的顶点所对应的终点, 顶点数组和ends[]数组共用一套下标//递归遍历终点, 取的该顶点连通的边的最终点, 返回最终点下标, 若点未被连通返回点本身private int getVertexEnd(int[] ends, int i) {while (ends[i] != 0) {i = ends[i];}return i;}private SideData[] getMST() {//表示最终结果数组的索引int index = 0;//用于保存已有最小生成树中的每个顶点在最小生成树中的终点int[] ends = new int[side_num];//创建最终结果数组. 用于保存最小生成树.SideData[] mst_res = new SideData[vertex_data.length - 1];//获取图中所有边的集合, 并从小到大排序SideData[] sides = getSides();sortSide(sides);//输出当前图的边的集合System.out.println("\n============show graph's sides============");showSide(sides);//开始遍历边集合sides, 进行逐步构造最小生成树并判断即将加入的边是否构成回路for (int i = 0; i < side_num; i++) {//获取到第i条边的第一个顶点, 即边的起点int p1 = getPosition(sides[i].start_point);//获取到第i条边的第二个顶点, 即边的终点int p2 = getPosition(sides[i].end_point);//获取p1,p2两个顶点在已有的最小生成树中的终点int end_p1 = getVertexEnd(ends, p1);int end_p2 = getVertexEnd(ends, p2);//判断是否会构成回路if(end_p1 != end_p2){//如果没有构成回路, 则设置p1的终点为p2ends[end_p1] = end_p2;//同时将这条边加入最小生成树当中mst_res[index++] = sides[i];}}//最终返回最小生成树集合return mst_res;}
}

2.3. 测试结果

  • 显而易见, 结果正确

058.克鲁斯卡尔(Kruskal)算法的原理以及解决最小生成树问题相关推荐

  1. 算法:通过克鲁斯卡尔(Kruskal)算法,求出图的最小生成树

    之前我给大家分享过用普利姆(Prim)算法来求出图的最小生成树(点我去看看),今天我再给大家分享一个也是求图的最小生成树的克鲁斯卡尔(Kruskal)算法 克鲁斯卡尔(Kruskal)算法,就相当于先 ...

  2. 数据结构与算法(7-3)最小生成树(普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法)

    目录 一.最小生成树简介 二.普里姆算法(Prim) 1.原理 2.存储 2-1.图顶点和权: 2-3. 最小生成树: 3.Prim()函数 3-1.新顶点入树 3-2.保留最小权 3-3. 找到最小 ...

  3. 对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的边。说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度

    对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的 边.说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度

  4. 【算法】克鲁斯卡尔 (Kruskal) 算法

    目录 1.概述 2.代码实现 2.1.并查集 2.2.邻接矩阵存储图 2.3.邻接表存储图 2.4.测试代码 3.应用 本文参考: <数据结构教程>第 5 版 李春葆 主编 1.概述 (1 ...

  5. 算法之克鲁斯卡尔(Kruskal)算法

    克鲁斯卡尔(Kruskal)算法 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法. 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路 具体做法:首先 ...

  6. 普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法

    图是一种基础又重要的数据结构,图的生成树是图的一个极小连通子图.最小生成树是无向连通网的所有生成树中边的权值之和最小的一棵生成树.求图的最小生成树可以牵引出很多经典的题目,例如在N个城市之间建立通讯网 ...

  7. Java实现之克鲁斯卡尔(Kruskal)算法

    一.问题引入 1.问题引入 1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通 2)各个站点的距离用边线表示(权),比如A-B距离12公里 3)问:如何修路保证各个站点都 ...

  8. 【数据结构与算法】克鲁斯卡尔(Kruskal)算法

    一,应用场景 公交站问题 1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通 2)各个站点的距离用边线表示(权),比如 A - B距离12公里 3)问:如何修路保证各个站 ...

  9. 普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法

    普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法 普里姆算法的基本思想: 取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w.添加顶点w的条件为:w 和已在生成树上的顶点v ...

  10. 7、最小生成树,克鲁斯卡尔(Kruskal)算法

    1)算法的基本思想: 前面我们学习过Prim算法,他是一种以某个节点出发,按权值递增的次序选择合适的边来构造最小生成树的方法,他的时间复杂度为O(n2),与顶点有关,而与边无边,所以适合求边稠密的图的 ...

最新文章

  1. LeetCode 26 号问题 删除数组中的重复项
  2. php 对象方法作为参数,在C++中对象如何作为参数传递和返回?(代码示例)
  3. 20165203 《网络对抗技术》week1 Kali的安装与配置
  4. 生死狙击服务器名字怎么修改,生死狙击端游怎么改名字,生死狙击端游怎么改名字?...
  5. GitHub使用详细流程(多人开发)
  6. html5播放器占用带宽情况,分享|用 bmon 查看网络带宽使用情况
  7. 如何把文档扫描保存到Google Drive中
  8. JS获取DropDownList的value值与text值
  9. Java中print、printf、println的区别(转载)
  10. 图文演示通过虚拟打印机生成pdf的使用技巧
  11. 关于在VC + + 2008 VCRedist安装时生成在根目录下的临时文件
  12. 3招教你花式导入Excel数据到JMP
  13. MATLAB绘制二元函数图像
  14. 算法设计与分析 SCAU11083 旅游背包(优先做)
  15. 微信公众号开发报错:错误代码:40164, 错误信息:invalid ip
  16. Unity 横向滚动ScrollView
  17. ROS常用的仿真软件
  18. 探索推荐引擎内部的秘密 - 推荐引擎初探
  19. 创业期间,应该怎么样坚持下去?如何从容面对困难?
  20. 关于强类型语言和无(弱)类型语言——致编程入门者

热门文章

  1. linux电子表格工具,Linux系统办公一条龙之电子表格Calc
  2. Camtasia如何给视频或者图片调色
  3. Netflix Web 性能案例研究
  4. am大学计算机科学,美国大学本科计算机科学专业排名一览
  5. 大数据技术应用于金融行业,主要有什么影响?
  6. css3 文字高光划过,CSS3实现一束光划过图片、和文字特效
  7. 一个点的经度和纬度,以这个点为圆心,1000米为半径,最大的经度和纬度,最小的经度和纬度
  8. 位置度标注方法图解_追踪主力-散户操盘实战图解:案例实操
  9. 直角四面体面积公式推导
  10. win7与internet时间同步出错_【时间同步出错】win7系统同步internet时间总是提示同步时出错的解决方法...