zoukankan      html  css  js  c++  java
  • 最小生成树,prime, kruskal与并查集算法

    一、定义:

    连通图:

      如果有路径使得顶点i,j相连。则称顶点i,j连通。

    最小生成树: 

      在含有n个顶点的连通图中,选取n-1个边,构成一个极小连通子图,使得各个边的权值和最小,则这个最小连通子图称为最小生成树。如图所示:

     

     

     

     

     

     

    则可以看出权值和为40的这个图为最小生成树。

     

    二、解法

    1、Prime算法  

    • 首先定义两个集合U,V用来存放顶点。V用来存放已经加入的点,U用来存放没有加入的点。
    • 从起点开始,将其放入集合V中
    • 以V为搜索框架,在U中寻找V不存在的点,使得点v与点u的边权值最小,将边vu加入集合E中
    • 重复此步骤,直到V、U顶点一样

    图解:

      

    Code:

     1 package algorithm;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 
     6 public class Prime {
     7     private static final int INF = 0x7fffffff;
     8 
     9     public static class Edge {
    10         char from;
    11         char to;
    12         int weight;
    13 
    14         public Edge(char from, char to, int weight) {
    15             this.from = from;
    16             this.to = to;
    17             this.weight = weight;
    18         }
    19     }
    20 
    21     public static class Graph {
    22         char[] nodes;
    23         int[][] matrix;
    24 
    25         public Graph(char[] nodes, int[][] matrix) {
    26             this.nodes = nodes;
    27             this.matrix = matrix;
    28         }
    29     }
    30 
    31     public static List<Edge> prime(Graph graph) {
    32         int n = graph.nodes.length;
    33         boolean[] visited = new boolean[n];
    34         visited[0] = true;
    35         List<Edge> res = new ArrayList<>();
    36 
    37         for (int a = 0; a < n-1; a++) {
    38             int from = 0, to = 0, min = INF;
    39             for (int i = 0; i < n; i++) {
    40                 if (visited[i]) {
    41                     for (int j = 0; j < n; j++) {
    42                         if (!visited[j] && j != i && graph.matrix[i][j] < min) {
    43                             min = graph.matrix[i][j];
    44                             from = i;
    45                             to = j;
    46                         }
    47                     }
    48                 }
    49             }
    50             visited[to] = true;
    51             res.add(new Edge(graph.nodes[from], graph.nodes[to], min));
    52         }
    53 
    54         return res;
    55     }
    56 
    57     public static void main(String[] args) {
    58         char[] nodes = {
    59                 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'
    60         };
    61 
    62         int[][] matrix = {
    63                 {0,   3,   INF, 10,  INF, INF, INF, INF},
    64                 {3,   0,   8,   INF, 7,   INF, INF, INF},
    65                 {INF, 8,   0,   INF, INF, 9,   INF, INF},
    66                 {10,  INF, INF, 0,   7,   2,   14,  INF},
    67                 {INF, 7,   INF, 7,   0,   INF, 9,   INF},
    68                 {INF, INF, INF, 2,   INF, 0,   7,   INF},
    69                 {INF, INF, INF, 14,  9,   7,   0,   6  },
    70                 {INF, INF, 9,   INF, INF, INF, 6,   0  }
    71         };
    72 
    73         Graph graph = new Graph(nodes, matrix);
    74 
    75         List<Edge> res = prime(graph);
    76         for(Edge edge : res){
    77             System.out.println("from " + edge.from + " to " + edge.to + " wight " + edge.weight);
    78         }
    79     }
    80 }

    结果:

      

     

     

     

     

     

    2、Kruskal算法

    步骤

    • 将图G的边按照权值从小到大排序得到边的集合 E,初始化极小连通子图V
    • 遍历E,从E中取出每个边e,如果e中的两个点和V不属于统一连通分量(将e加进V中不构成环),则将e加入V中
    • 重复上述过程,直到已经加入了n-1条边,其中n为顶点个数。

    图解

    如果要加入一个边e到一个图G,判断新图有没有环:

    • 记录图G中所有点f的终点t
    • 分别寻找边e两点a,b的终点
    • 如果边a,b的终点一样则,a,b已经在一个连通图中,如果加入e边,则形成了环
    • 现有算法并查集算法(合并-查找)

    Code:

      

     1 package algorithm;
     2 
     3 import java.util.ArrayList;
     4 import java.util.Collections;
     5 import java.util.List;
     6 
     7 public class Kruskal {
     8     private static int[] endsOfNode;
     9     private static int INF = 0x7fffffff;
    10 
    11     //定义边类
    12     public static class Edge implements Comparable<Edge> {
    13         int from;
    14         int to;
    15         int weight;
    16 
    17         public Edge(int from, int to, int weight) {
    18             this.from = from;
    19             this.to = to;
    20             this.weight = weight;
    21         }
    22 
    23         @Override
    24         public int compareTo(Edge o1) {
    25             return this.weight - o1.weight;
    26         }
    27     }
    28 
    29     //定义图类
    30     public static class Graph {
    31         char[] nodes;
    32         int[][] matrix;
    33 
    34         public Graph(char[] nodes, int[][] matrix) {
    35             this.nodes = nodes;
    36             this.matrix = matrix;
    37         }
    38     }
    39 
    40     //得到一个连通图的终点, 递归更新每一个点的终点,查找操作,并且将每一个点的终点都统一成一级结构,64,65,70行为合并 join操作
    41     public static int getEndOfNode(int index) {
    42         if (endsOfNode[index] != index) endsOfNode[index] = getEndOfNode(endsOfNode[index]);
    43         return index;
    44     }
    45 
    46     public static List<Edge> kruskal(Graph graph) {
    47         int n = graph.nodes.length;
    48         endsOfNode = new int[n];
    49 
    50         for (int i = 0; i < n; i++) endsOfNode[i] = i;
    51 
    52 
    53         List<Edge> edges = new ArrayList<>();
    54         for (int i = 0; i < n; i++)
    55             for (int j = i + 1; j < n; j++)
    56                 if (graph.matrix[i][j] != INF)
    57                     edges.add(new Edge(i, j, graph.matrix[i][j]));
    58 
    59         Collections.sort(edges);
    60 
    61         int c = 0;
    62         List<Edge> res = new ArrayList<>();
    63         for (Edge edge : edges) {
    64             int x = getEndOfNode(edge.from);
    65             int y = getEndOfNode(edge.to);
    66   
    67             if(x == y) continue;
    68             if (c++ == n - 1) break;
    69             res.add(edge);
    70             endsOfNode[x] = y;
    71         }
    72         return res;
    73     }
    74 
    75 
    76     public static void main(String [] args){
    77         char[] nodes = {
    78                 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'
    79         };
    80 
    81         int[][] matrix = {
    82                 {0,   3,   INF, 10,  INF, INF, INF, INF},
    83                 {3,   0,   8,   INF, 7,   INF, INF, INF},
    84                 {INF, 8,   0,   INF, INF, 9,   INF, INF},
    85                 {10,  INF, INF, 0,   7,   2,   14,  INF},
    86                 {INF, 7,   INF, 7,   0,   INF, 9,   INF},
    87                 {INF, INF, INF, 2,   INF, 0,   7,   INF},
    88                 {INF, INF, INF, 14,  9,   7,   0,   6  },
    89                 {INF, INF, 9,   INF, INF, INF, 6,   0  }
    90         };
    91 
    92         Graph graph = new Graph(nodes, matrix);
    93 
    94         List<Edge> res = kruskal(graph);
    95         for(Edge edge : res){
    96             System.out.println("from " + nodes[edge.from] + " to " + nodes[edge.to] + " wight " + edge.weight);
    97         }
    98     }
    99 }

    结果:

    3、并查集算法

    1、

          在kruskal算法中,如果我们要新加入一条边到一个子图当中,需要判断如果加入改边,是否会形成环。如图所示,如果已经加入了边E(a,b), E(b,c), 现在要加入E(a,c)。需要先判断加入边E(a,c)是否形成了环,即判断点a,c是否在同一个连通子图中。

      

    2、

      那么如何判断呢?我们可以构造一个连通图。然后在这个联通图中,从边的一点出发去寻找另一个点。如果找到这两个点在同一个连通图中,否则不在一个连通图中。

      这是一个简单粗暴最容易想到的方法。如果A,B在同一个连通子图中,每次查找的时间复杂度是O(n2),n为连通子图的节点个数。

    3、Can we do better?

      如果能设计一个结构,使得在一个连通子图中所有子节点都指向一个父节点,那么查找的时间复杂度会是O(1),更新每个子节点的时间复杂度是O(n).

      实际做法是

    • 记录每个子节点C的父节点F
    • 如果父节点F有了父节点G,则更新F的每一个子节点的父节点为G
    • 要保证边两端节点命名的顺序,例如E(a,b) ,E(b,c)要有字母顺序

    4、Code(非递归版本)

        public int find(int node){
            int end = node;
    
            while (end != ends[end]) end = ends[end]; //查找根节点
    
            while (node != end){  //更新每个子节点的父节点为 end
                int nt = ends[node];
                ends[node] = end;
                node = nt;
            }
    
            return end;
        }
    
    
        public void join(int a, int b){
            int ae = union(a);
            int be = union(b);
            if (ae != be){
                ends[ae] = be; //插入边的右节点 为 左节点的父节点
            }
        }

    递归代码更简洁:

       public int find(int node){
            if(ends[node] != node) ends[node] = find(node);
            return node;
        }
        
        public void join(int a, int b){
            int ae = union(a);
            int be = union(b);
            if (ae != be){
                ends[ae] = be; //插入边的右节点 为 左节点的父节点
            }
        }
  • 相关阅读:
    通过git向github提交项目
    git连接github mac
    char如何储存3个字节或者4个字节
    centOS 7安装jdk
    在移动端语言react中使用video.js
    小程序自定义头部navbar组件
    git常用指令汇总学习
    react表单
    react从入门到熟悉(回顾react)
    react生命周期
  • 原文地址:https://www.cnblogs.com/ylxn/p/12287004.html
Copyright © 2011-2022 走看看