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,蓝桥杯-城市建设。

  • 相关阅读:
    杂记 后台代码取DataSource中的值,和不间断滚动JS
    利用IHttpModule实现URL地址转发功能
    在ASP.NET中跟踪和恢复大文件下载
    小记,取GB2312汉字的首字母
    小记,提供文件下载,并控制下载速度
    黑客知识系列之木马程序隐身的技术
    经典算法C#四种排序算法
    跟我学做c#皮肤美化(六)
    【转】xPath语法介绍
    一个httpwebrequest异步下载的例子
  • 原文地址:https://www.cnblogs.com/xiaoyh/p/10414579.html
Copyright © 2011-2022 走看看