zoukankan      html  css  js  c++  java
  • 最小生成树 java实现

    最小生成树

    最小生成树的定义:最小生成树是在一个给定的无向图G(V,E)中求一颗树T,使得这棵树拥有图G中所有顶点,且所有边都是来自图G中的边,并且满足整棵树的边权之和最小。

    最小生成树三个性质:

    1. 最小生成树是树,因此其边数等于顶点数减一,且树内一定不会有环
    2. 对给定的图,其最小生成树可以不唯一,但边权之和一定唯一
    3. 最小生成树是在无向图上面生成的,因此其根节点可以是这颗树上面的任意一个节点。如果题目中涉及最小生成树的输出,为了让生成树唯一,一般会直接给出根节点,只需要给出节点作为根节点来求解最小生成树即可

    问题种类:铺设公路

    两种解决算法

    1. 普利姆算法(prim)

      1. 朴素版Prim算法(稠密图),时间复杂度O(n^2)
      2. 堆优化版Prim算法(稀疏图),时间 复杂度O(mlogn)(很少使用
    2. 克鲁斯卡尔算法(Kruskal)(稀疏图

    朴素版prim算法

    假如有5个节点,6条边,
    A B 3
    A C 1
    B C 2
    A E 4
    C D 5
    C E 6
    T 表示当前包含所有节点的集合,U 表示NULL。假如从A开始,(将A加入到U中)
    第一步:选中A到各个节点中权重最小的节点
    第二步:判断该节点是否被访问过,如果没有被访问过,则将该节点加入到集合U中
    第三步:更新其他节点到集合U的距离
    第四步:选择到集合U最近的点,重复第二步

    时间复杂度是 (O(n2+m)), n 表示点数,m 表示边数

    static int n;      // n表示点数
    static int INF = 0x3f3f3f3f;
    static int[][] g = new int[N][N];        // 邻接矩阵,存储所有边
    static int[] dist = new int[N];        // 存储其他点到当前最小生成树(集合)的距离
    static boolean st = new int[N];     // 存储每个点是否已经在生成树中
    
    // 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
    int prim(){
          Arrays.fill(dist, 0x3f);
    
        //所有生成树里的边的长度之和的最小值
        int res = 0;
        for (int i = 0; i < n; i ++ ){
            int t = -1;
            for (int j = 1; j <= n; j ++ ) {
               if (st[j] != false && (t == -1 || dist[t] > dist[j])) {
                   t = j;
                }
            }
    
            if (i != 0 && dist[t] == INF) return INF;
            if (i != 0) res += dist[t];
            st[t] = true;		
            for (int j = 1; j <= n; j ++ ) {
                        dist[j] = min(dist[j], g[t][j]);          
            }
        }
    
        return res;
    }
    

    对应题目

    /**
     *prime算法实现
     * 输入样例:
     *      4 5
     *      1 2 1
     *      1 3 2
     *      1 4 3
     *      2 3 2
     *      3 4 4
     * 输出样例:
     *      6
     */
    public class PrimAlgorithm {
        int INF = 500000;
        private Scanner sc = new Scanner(System.in);
    
        public void run() {
            int n = sc.nextInt();
            int m = sc.nextInt();
    
            int[][] graph = new int[n + 1][n + 1];
            for (int i = 0; i < n + 1; i++) {
                Arrays.fill(graph[i], INF);
            }
    
            for (int i = 0; i < m; i++) {
                int x = sc.nextInt();
                int y = sc.nextInt();
                int z = sc.nextInt();
                //无向图
                graph[x][y] = Math.min(graph[x][y], z);
                graph[y][x] = graph[x][y];
                graph[x][x] = 0;
                graph[y][y] = 0;
            }
    
            int res = prim(graph);
            if (res == INF) {
                System.out.println("impossible");
            } else {
                System.out.println(res);
            }
        }
    
        public int prim(int[][] graph) {
            // graph长度是n+1
            int n = graph.length - 1;
            // dist[i] 表示的是i到生成树集合的最小距离
            int[] dist = new int[n + 1];
            // 给dist初始化为无穷
            Arrays.fill(dist, INF);
            int res = 0;
            // 存储对应的边是否被访问过
            boolean[] st = new boolean[n + 1];
            // 因为之前存储graph的时候,是从下标1开始的,所以这里遍历时候就是从1开始
            for (int i = 1; i < n + 1; i++) {
                int t = -1;
                for (int j = 1; j < n + 1; j++) {
                    if (st[j] == false && (t == -1 || dist[t] > dist[j])) t = j;
                }
                // 避免第一次循环的时候,直接返回
                if (i != 1 && dist[t] == INF) return INF;
                // 表示节点t已经被访问过
                st[t] = true;
                if (i != 1) {
                    // 将节点添加到生成树中
                    res += dist[t];
    //                System.out.println(res);
                }
                // 生成树是一个集合
                // 因为生成树添加了新的节点,所以更新其他未被访问的点到生成树的最小距离,
                for (int j = 1; j < n + 1; j++) {
                    dist[j] = Math.min(dist[j], graph[t][j]);
                }
            }
            return res;
        }
    
        public static void main(String[] args) {
            new PrimAlgorithm().run();
        }
    }
    
    

    Kruskal算法

    参考:https://blog.csdn.net/coslay/article/details/47756917

    前置知识:排序和并查集

    基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。
    具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。

    时间复杂度是 (O(mlogm)), n 表示点数,m 表示边数

    思路:

    1. 将所有边按权重w从小到大排序;

    2. 枚举每条边的顶点a,b

      if (a,b 不在同一个集合中(并查集)) 将这条边加入集合中

    int n, m;       // n是点数,m是边数
    int[] p = new int[N];       // 并查集的父节点数组
    
    class Edge{     // 存储边
        int a, b, w;
            Edge(int a, int b, int c) {
              this.a = a;this.b = b; this.c = c;
        }
    };
    
    int find(int x){     // 并查集核心操作
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    
    public int kruskal(){
        Arrays.sort(edge, new Comparator<Edge>() {
          @Override
          public int compare(Edge o1, Edge o2) {
            if(o1.w<o2.w)   return -1;
            else if(o1.w>o2.w)  return 1;
            else return 0;
          }
        });
    
        for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集
    
        int res = 0, cnt = 0;
        for (int i = 0; i < m; i ++ ) {
            int a = edges[i].a, b = edges[i].b, w = edges[i].w;
    
            a = find(a);
            b = find(b);
            if (a != b) {    // 如果两个连通块不连通,则将这两个连通块合并
                p[a] = b;
                res += w;
                cnt ++ ;
            }
        }
    
        if (cnt < n - 1) return INF;
        return res;
    }
    

    题目:

    /**
     * kruskal算法实现
     * 输入样例:
     *      4 5
     *      1 2 1
     *      1 3 2
     *      1 4 3
     *      2 3 2
     *      3 4 4
     * 输出样例:
     *      6
     */
    public class KruskalAlgorithm {
        int INF = Integer.MAX_VALUE / 2;
        private Scanner sc = new Scanner(System.in);
        private int n;
        private int m;
    
        public static void main(String[] args) {
            new KruskalAlgorithm().run();
        }
    
        // 并查集
        private int find(int[] p, int x) {
            if (p[x] != x) p[x] = find(p, p[x]);
            return p[x];
        }
    
        private void run() {
            n = sc.nextInt();
            m = sc.nextInt();
            // 存储边
            List<Edge> edgeList = new ArrayList<>();
            // 下标从1开始,因为节点的值是从1开始的
            int[] p = new int[n + 1];
            for (int i = 1; i <= n; i++) {
                p[i] = i;
            }
          
            for (int i = 0; i < m; i++) {
                int x = sc.nextInt();
                int y = sc.nextInt();
                int z = sc.nextInt();
                edgeList.add(new Edge(x, y, z));
            }
            int res = kruskal(edgeList, p);
            if (res == INF) {
                System.out.println("impossible");
            } else {
                System.out.println(res);
            }
        }
    
        private int kruskal(List<Edge> edgeList, int[] p) {
            int res = 0;
            int cnt = 0;
            // 按照w 权重的大小来排序
            Collections.sort(edgeList);
            for (int i = 0; i < edgeList.size(); i++) {
                int a = edgeList.get(i).getA();
                int b = edgeList.get(i).getB();
                int w = edgeList.get(i).getW();
                // 分别找到a、b对应的集合的根
                a = find(p, a);
                b = find(p, b);
                // 如果根相同表示a、b在同一个集合中,如果添加进去,会形成回路,则该边不能添加到集合中
                // 如果根不相同表示a、b不在同一个集合中,则该边能添加到集合中
                if (a != b) {
                    p[a] = b;
                    res += w;
                    // 计算添加进去的边
                    cnt++;
                }
            }
            // n 是点的数量,n个点最少是n-1条边,如果少于n-1条边,则表示 n个点并没有全部使用到,所以不成立
            if (cnt < n - 1) return INF;
            return res;
        }
    }
    
    class Edge implements Comparable<Edge> {
        int a, b, w;
        public Edge(int a, int b, int w) {
            // a,b 为节点
            this.a = a;
            this.b = b;
            // w 为节点之间的权重
            this.w = w;
        }
        public int getA() {
            return a;
        }
        public int getB() {
            return b;
        }
        public int getW() {
            return w;
        }
        // 设置edge的排序规则,按照w的大小排序
        @Override
        public int compareTo(Edge edge) {
            return Integer.compare(w, edge.w);
        }
    }
    
  • 相关阅读:
    php的函数
    php字符串
    PDA触屏的终极解决办法
    数字万用表 选购指南
    WindowsXp Sp2 英文版
    访问局域网某台电脑时提示:无法访问,你可能没有权限使用网络资源.的解决办法
    中华人民共和国国家标准职工工伤与职业病致残程度鉴定
    删除所有设备驱动的批处理
    如何制作Win XP操作系统映像文件
    使用批处理和devcon.exe来控制 Windows 的设备
  • 原文地址:https://www.cnblogs.com/zhangyuestudying/p/13831861.html
Copyright © 2011-2022 走看看