到底什么是最小生成树

最小生成树算法应用范围比较广,例如在网络的铺设当中难免会出现环路,需要要生成树算法来取出网络中的环,防止网络风暴的发生。那到底什么是最小生成树呢?我这里就不给严谨的定义了,这种定义网上一搜一大堆,但是往往严谨的定义都不太容易理解。下面我就给出一个更容易理解的定义。

在理解“最小生成树”之前,我们需要理解什么是“生成树”。

生成树的概念在一个连通图中取出这个图的全部顶点和一部分边形成的一个无环图就是这个连通图的生成树。例如在下图中,任意取出图中的一条边都使得这个图中没有环路,也就形成了一棵树。

有人可能会说了,右边生成的图也不像是一棵树呀,其实没有环的图就是一颗树,只不过树中每个节点的度都是不定的。

理解完生成树的概念后,我想最小生成树的概念就很容易理解了:在一个连通图的所有生成树中,边的权重之和最小的那棵树称为最小生成树。话不多说,给朕来张图:

在所有生成树中只有这棵树的权重最小,为6。

因为这个图太简单了,我们一眼就可以看出来去除A<——>B的的这条边后就是最小生成树,那如果图变得非常复杂,就比如这样:

此时我们就需要去寻求最小生成树算法的帮助了。其实算法也就是解决特定问题的一种套路或者说规律,如果自己找不出一些问题解决的思路,就可以去学习前人总结出来的规律了。

Kruskal算法的实现思路

最小生成树算法有多种,例如:Kruskal,Prim。我们这里来对Prim算法不做解释。

第一步:按照边的权重对边进行排序

第二步:从上至下依次取出边,每次取的边如果在树中形成了环路则必须丢弃。直到最后一条顶点被连接在树中则最小生成树生成。

在取出(D,E)这条边时由于与(C,D)、(C,E)产生了回路,所以丢弃这条边。

这里我们就来到了问题的重点了,我们到底如何判断新加进来的边到底会不会形成一个环路呢?这里我们可以使用并查集的。

判断回路的产生

初始状态我们将图中每一个结点都看成一颗树,就比如这样:

此时我们取出排序边集中的第一条边(C,D),我们发现C、D两个节点来自不同的树,这就说明这条边的加入不会形成环路。此时我么需要将D树置成C的子树。就比如这样:

此时我们取出第二条边(C,A),我们发现C、A两个节点属于不同的树,所以(C,A)边的加入不会成环,我们将这条边加到最小生成树的边集当中,此时我们将需要将A树置为C的子树,就比如这样:

我们取出第三条边(C,E),发现C、E仍然属于两个不同的树,所以我们依旧将这条边加入最小生成树的边集当中。然后将E树置为C树的子树,就比如这样:

重点来了,我们取出第四条边(D,E),发现这两个节点都来自同一个树,说明如果我们将(D,E)边加入到生成树的边集中就会形成环路,所以(D,E)这条边就需要舍弃。

我们取出第五条边:(A,B),发现A、B属于不同的节点,这就代表这个边的加入不会成环,我们将这条边加入最小生成树的边集当中。这是我们发现边集中边的数量加一刚好等于节点数,这也就说明,每个节点都已将包含在最小生成树的边集当中了,也就不需要继续向下取排序边集了。

此时我们将边集中的边重构成一张图,也就是一个最小生成树了:

上面就是整个Kruskal的思路

Kruskal算法的实现

/**
* 图的表示形式有多种,例如:邻接表、邻接矩阵、边集等。我们这里使用边集来表示图
*/
public class Hello {
public static class Edge {
String start;
String end;
int distance; @Override
public String toString() {
return start + "——>" + end;
} public Edge(String start, String end, int distance) {
this.start = start;
this.end = end;
this.distance = distance;
if (graphNode.get(start) == null) {
graphNode.put(start, new Node(start));
nodeNum++;//图的节点数加一
}
if (graphNode.get(end) == null) {
graphNode.put(end, new Node(end));
nodeNum++;
} }
} public static class Node {
String content;
Node parent; public Node(String content) {
this.content = content;
}
} public static List<Edge> edges; public static Set<Edge> tree = new HashSet<>();//用边集表示最小生成树 public static int nodeNum = 0;//图的节数点 public static Map<String, Node> graphNode = new HashMap<>(); public static void main(String[] args) {
//生成一个带权图(用边集表示图)
buildEdges(); //对图的边集进行排序
edges.sort(new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return o1.distance - o2.distance;
}
}); //产生最小生成树
for (Edge edge : edges) {
if (tree.size() + 1 == nodeNum) {//如果 边树+1等于图的节点数,就证明最小生成树已经生成了,也就不用遍历后面的带权边了
break;
}
if (isOk(edge)) {
tree.add(edge);
}
} //打印最小生成树的边
for (Edge edge : tree) {
System.out.println(edge.toString());
}
} /**
* 为边集里面添加元素,也就是构建一个图
*/
public static void buildEdges() {
edges = new ArrayList<>();
edges.add(new Edge("C", "D", 1));
edges.add(new Edge("C", "A", 1));
edges.add(new Edge("C", "E", 2));
edges.add(new Edge("A", "B", 3));
edges.add(new Edge("D", "E", 3));
edges.add(new Edge("B", "C", 5));
edges.add(new Edge("B", "E", 6));
edges.add(new Edge("B", "D", 7));
edges.add(new Edge("A", "D", 2));
edges.add(new Edge("A", "E", 9));
} /**
* 获取并查集中指定元素的根节点
*
* @param node
* @return
*/
public static Node getRootNode(Node node) {
Node root = node;
while (root.parent != null) {
root = root.parent;
}
if (node != root) {
node.parent = root;
}
return root;
} /**
* 判断这条边集是否能够加到最小生成树的边集中。
*
* @param edge
* @return true表示可以成为最小生成树的一条边
*/
public static boolean isOk(Edge edge) {
Node node1 = graphNode.get(edge.start);
Node node2 = graphNode.get(edge.end);
Node root1 = getRootNode(node1);
Node root2 = getRootNode(node2);
if (root1 != root2) {//如果边的两个点当前不属于一个集,那么这条边作为树的一条边就不会形成环路。
root2.parent=root1;//node2所在的树并成node1子树(两棵树合并)
return true;
}
return false;
}
}

运行结果: 

最小生成树Kruskal算法的实现原理的更多相关文章

  1. 万字长文,以代码的思想去详细讲解yolov3算法的实现原理和训练过程,Visdrone数据集实战训练

    以代码的思想去详细讲解yolov3算法的实现原理和训练过程,并教使用visdrone2019数据集和自己制作数据集两种方式去训练自己的pytorch搭建的yolov3模型,吐血整理万字长文,纯属干货 ...

  2. 求最小生成树——Kruskal算法和Prim算法

    给定一个带权值的无向图,要求权值之和最小的生成树,常用的算法有Kruskal算法和Prim算法.这两个算法其实都是贪心思想的使用,但又能求出最优解.(代码借鉴http://blog.csdn.net/ ...

  3. 贪心算法-最小生成树Kruskal算法和Prim算法

    Kruskal算法: 不断地选择未被选中的边中权重最轻且不会形成环的一条. 简单的理解: 不停地循环,每一次都寻找两个顶点,这两个顶点不在同一个真子集里,且边上的权值最小. 把找到的这两个顶点联合起来 ...

  4. Kruskal算法的实现

    #include "stdio.h" #include "stdlib.h" struct edge { int m; int n; int d; }a[]; ...

  5. 冷饭新炒:理解Snowflake算法的实现原理

    前提 Snowflake(雪花)是Twitter开源的高性能ID生成算法(服务). 上图是Snowflake的Github仓库,master分支中的REAEMDE文件中提示:初始版本于2010年发布, ...

  6. 转载:最小生成树-Prim算法和Kruskal算法

    本文摘自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html 最小生成树-Prim算法和Kruskal算法 Prim算 ...

  7. java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现

    java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...

  8. 最小生成树Prim算法和Kruskal算法

    Prim算法(使用visited数组实现) Prim算法求最小生成树的时候和边数无关,和顶点树有关,所以适合求解稠密网的最小生成树. Prim算法的步骤包括: 1. 将一个图分为两部分,一部分归为点集 ...

  9. 最小生成树之Kruskal算法和Prim算法

    依据图的深度优先遍历和广度优先遍历,能够用最少的边连接全部的顶点,并且不会形成回路. 这样的连接全部顶点并且路径唯一的树型结构称为生成树或扩展树.实际中.希望产生的生成树的全部边的权值和最小,称之为最 ...

  10. Atitit paip.对象方法的实现原理与本质.txt

    Atitit paip.对象方法的实现原理与本质.txt 对象方法是如何实现的1 数组,对象,字典1 对象方法是如何实现的 这显然是一个对象方法调用.但对象方法是如何实现的呢?在静态语言中,因为有编译 ...

随机推荐

  1. cas登录成功后跳转地址和退出后跳转首页

    cas登录成功后跳转地址和退出后跳转首页 CAS版本5.3 1.登录页面 的登录链接地址为 login.html ...<span v-if="username == ''" ...

  2. JVM—垃圾收集器

    JVM-垃圾收集器 什么是垃圾 没有被引用的对象就是垃圾. 怎么找到垃圾 引用计数法 当对象引用消失,对象就称为垃圾. 对象消失一个引用,计数减去一,当引用都消失了,计数就会变为0.此时这个对象就会变 ...

  3. sql 语句系列(插入系列)[八百章之第五章]

    复制数据到另外一个表 这个不解释,只是自我整理. insert EMP_EAST (DEPTNO,DNAME,LOC) select DEPTNO,DNAME,LOC from DEPT where ...

  4. python数据库迁移

    实际操作命令 1,python 文件.py db init 2,python xx.py db migrate -m '版本描述' 3,python xx.py db upgrade 4,python ...

  5. 树莓派和esp8266之间使用tcp协议通信

    树莓派代码: from flask import Flask, render_template import socket import threading app = Flask(__name__) ...

  6. 《c#高级编程》第5章C#5.0中的更改(十)——异步编程

    C#异步编程是一种在单线程上实现并发执行的技术,它通过使用异步方法.任务等高级概念,使得应用程序能够更好地响应用户操作.处理大量数据和操作外部资源.C#异步编程的核心概念包括: 异步方法:使用 asy ...

  7. 记录如何用php做一个网站访问计数器的方法

    简介创建一个简单的网站访问计数器涉及到几个步骤,包括创建一个用于存储访问次数的文件或数据库表,以及编写PHP脚本来增加计数和显示当前的访问次数. 方法以下是使用文件存储访问次数的基本步骤: 创建一个文 ...

  8. 力扣434(java)-字符串中的单词个数(简单)

    题目: 统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符. 请注意,你可以假定字符串里不包括任何不可打印的字符. 示例: 输入: "Hello, my name is John& ...

  9. Docker 安装,常用命令

    安装Docker 官方所有操作系统安装教程:Install Docker Engine on CentOS | Docker Documentation,其中CentOS安装docker引擎的代码: ...

  10. 3分钟创建Serverless Job 定时获取新闻热搜

    简介: 7月25号-8月2号工作日期间,完成场景搭建,即可获得夏日清凉小风扇(每日限量)和阿里云代金券! 云起实验室,由阿里云提供的零门槛云上实践平台.一键预置实验环境,提供详细的实验手册指导,快速体 ...