zoukankan      html  css  js  c++  java
  • 最小生成树之Kruskal(克鲁斯卡尔)算法

    学习最小生成树算法之前我们先来了解下下面这些概念:

      树(Tree):如果一个无向连通图中不存在回路,则这种图称为树。

      生成树 (Spanning Tree):无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树。生成树是连通图的极小连通子图。这里所谓极小是指:若在树中任意增加一条边,则将出现一条回路;若去掉一条边,将会使之变成非连通图。

      最小生成树(Minimum Spanning Tree,MST):或者称为最小代价树Minimum-cost Spanning Tree:对无向连通图的生成树,各边的权值总和称为生成树的权,权最小的生成树称为最小生成树。常用于网络构建等建设性问题的优化。

    构成生成树的准则有三条:

      1、 必须只使用该网络中的边来构造最小生成树。

      2、必须使用且仅使用n-1条边来连接网络中的n个顶点

      3、不能使用产生回路的边。

      构造最小生成树的算法主要有:克鲁斯卡尔(Kruskal)算法和普利姆(Prim)算法他们都遵循以上准则。

      克鲁斯卡尔算法的基本思想是以边为主导地位,始终选择当前可用(所选的边不能构成回路)的最小权植边。所以Kruskal算法的第一步是给所有的边按照从小到大的顺序排序。这一步可以直接使用库函数qsort或者sort。接下来从小到大依次考察每一条边(u,v)。

    具体实现过程如下:

      1、设一个有n个顶点的连通网络为G(V,E),最初先构造一个只有n个顶点,没有边的非连通图T={V,空},图中每个顶点自成一格连通分量。

      2、在E中选择一条具有最小权植的边时,若该边的两个顶点落在不同的连通分量上,则将此边加入到T中;否则,即这条边的两个顶点落到同一连通分量      上,则将此边舍去(此后永不选用这条边),重新选择一条权植最小的边。

      3、如此重复下去,直到所有顶点在同一连通分量上为止。

     1 import java.util.ArrayList;
     2 import java.util.Collections;
     3 import java.util.HashMap;
     4 import java.util.HashSet;
     5 import java.util.List;
     6 import java.util.Map;
     7 import java.util.Set;
     8 
     9 public class Kruskal {
    10     private final List<Edge> edgeList;
    11       private final int n;//总顶点数
    12 
    13       private Set<Edge> T = new HashSet<>();//生成树的边集
    14       private Map pntAndNode = new HashMap();
    15 
    16       public Set<Edge> getT() {
    17         buildMST();
    18         return T;
    19       }
    20 
    21       public Kruskal(List<Edge> edgeList, int n) {
    22         this.edgeList = edgeList;
    23         //为每个顶点建立一个并查集的点
    24         for (Edge edge : edgeList) {
    25           pntAndNode.put(edge.getStart(), new UnionFind.UFNode());
    26           pntAndNode.put(edge.getEnd(), new UnionFind.UFNode());
    27         }
    28         this.n = n;
    29       }
    30 
    31       public static void main(String[] args) {
    32         List<Edge> edgeList = build();
    33         Kruskal obj = new Kruskal(edgeList, 5);
    34         // obj.buildMST();
    35         for (Edge e : obj.getT()) {
    36           System.out.println(e);
    37         }
    38       }
    39 
    40       private static List<Edge> build() {
    41         List<Edge> l = new ArrayList<>();
    42         l.add(new Edge("C", "D", 1));
    43         l.add(new Edge("C", "A", 1));
    44         l.add(new Edge("C", "E", 8));
    45         l.add(new Edge("A", "B", 3));
    46         l.add(new Edge("D", "E", 3));
    47         l.add(new Edge("B", "C", 5));
    48         l.add(new Edge("B", "E", 6));
    49         l.add(new Edge("B", "D", 7));
    50         l.add(new Edge("A", "D", 2));
    51         l.add(new Edge("A", "E", 9));
    52 
    53         return l;
    54       }
    55 
    56       /*构建MST的核心方法*/
    57       private void buildMST() {
    58         Collections.sort(edgeList);//排序
    59         //迭代
    60         for (Edge e : edgeList) {
    61           if (!ok(e))
    62             continue;
    63           //确认过了,就把边都加入
    64           T.add(e);
    65 
    66           if (T.size() == n - 1)
    67             return;//生成树的边数==总顶点数-1 =》 所有点都已经连接
    68         }
    69       }
    70 
    71       //并查集中查询e  的起点和终点是否在一个集中
    72       private boolean ok(Edge e) {
    73         UnionFind.UFNode x = (UnionFind.UFNode) pntAndNode.get(e.getStart());
    74         UnionFind.UFNode y = (UnionFind.UFNode) pntAndNode.get(e.getEnd());
    75         if (UnionFind.find(x) != UnionFind.find(y)) {//在不同的集中
    76           UnionFind.union(x, y);//合并并返回true
    77           return true;
    78         }
    79         return false;
    80       }
    81 
    82 }
    View Code

      最关键的地方在于“连通分量的查询合并”,需要知道任意两个点是否在同一连通分量中,还需要合并两个连通分量。这个问题正好可以用并查集完美的解决

      并查集(Union-Find set)这个数据结构可以方便快速的解决这个问题。基本的处理思想是:初始时把每个对象看作是一个单元素集合;然后依次按顺序读入联通边,将连通边中的两个元素合并。在此过程中将重复使用一个搜索(Find)运算,确定一个集合在那个集合中。当读入一个连通边(u,v)时,先判断u和v是否在同一个集合中,如果是则不用合并;如果不是,则用一个合并(Union)运算把u、v所在集合合并,使得这两个集合中的任意两个元素都连通。因此并查集在处理时,主要用到搜索合并两个运算。

      为了方便并查集的描述与实现,通常把先后加入到一个集合中的元素表示成一个树结构,并用根结点的序号来表示这个集合。因此定义一个parent[n]的数组,parent[i]中存放的就是结点i所在的树中结点i的父亲节点的序号。例如,如果parent[4]=5,就是说4号结点的父亲结点是5号结点。约定:如果i的父结点(即parent[i])是负数,则表示结点i就是它所在的集合的根结点,因为集合中没有结点的序号是负的;并且用负数的绝对值作为这个集合中所含结点的个数。例如,如果parent[7]=-4,说明7号结点就是它所在集合的根结点,这个集合有四个元素。初始时结点的parent值为-1(每个结点都是根结点,只包含它自己一个元素)。

      实现并查集数据结构主要有2个函数。

     1 import java.util.HashSet;
     2 import java.util.Set;
     3 
     4 public class UnionFind {
     5 
     6     public static UFNode find(UFNode x) {
     7         UFNode p = x;
     8         Set<UFNode> path = new HashSet<UFNode>();
     9         // 记录向上追溯的路径上的点
    10         while (p.parent != null) {
    11             path.add(p);
    12             p = p.parent;
    13         }
    14         // 这些点的parent全部指向这个集的代表
    15         for (UFNode ppp : path) {
    16             ppp.parent = p;
    17         }
    18         // root
    19         return p;
    20 
    21     }
    22 
    23     public static void union(UFNode x, UFNode y) {
    24         find(y).parent = find(x);
    25     }
    26 
    27     public static class UFNode {
    28         UFNode parent;
    29     }
    30 }
    View Code

      最后贴出边集的代码

     1 /**
     2  * 边 的封装
     3  * 边集可以用来表示图
     4  */
     5 public class Edge<T> implements Comparable<Edge>  {
     6     private T start;
     7       private T end;
     8       private int distance;
     9 
    10       public Edge(T start, T end, int distance) {
    11         this.start = start;
    12         this.end = end;
    13         this.distance = distance;
    14       }
    15 
    16       public T getStart() {
    17         return start;
    18       }
    19 
    20       public void setStart(T start) {
    21         this.start = start;
    22       }
    23 
    24       public T getEnd() {
    25         return end;
    26       }
    27 
    28       public void setEnd(T end) {
    29         this.end = end;
    30       }
    31 
    32       public int getDistance() {
    33         return distance;
    34       }
    35 
    36       public void setDistance(int distance) {
    37         this.distance = distance;
    38       }
    39 
    40       @Override
    41       public String toString() {
    42         return start + "->" + end + ":" + distance;
    43       }
    44 
    45       @Override
    46       public int compareTo(Edge obj) {
    47         int targetDis = obj.getDistance();
    48         return distance > targetDis ? 1 : (distance == targetDis ? 0 : -1);
    49       }
    50 }
    View Code

      最后运行Kruskal类的结果为:

      例题:POJ- 1287,蓝桥杯-城市建设。

  • 相关阅读:
    [SSRS] Use Enum values in filter expressions Dynamics 365 Finance and Operation
    Power shell deploy all SSRS report d365 FO
    display method in Dynamics 365 FO
    How To Debug Dynamics 365 Finance and Operation
    Computed columns and virtual fields in data entities Dynamics 365
    Azure DevOps for Power Platform Build Pipeline
    Create readonly entities that expose financial dimensions Dynamics 365
    Dataentity call stack dynamics 365
    Dynamics 365 FO extension
    Use singletenant servertoserver authentication PowerApps
  • 原文地址:https://www.cnblogs.com/xiaoyh/p/10414579.html
Copyright © 2011-2022 走看看