目录

关于最小生成树的一切


初识最小生成树

首先,小伙伴们可能要冒出第一个问题了。什么是生成树?生成树 指的是「无向图」中,具有该图的 全部顶点 且 边数最少 的连通子图。「图8. 生成树」中,所有粉色线条组成的一棵树[(A, B), (A, C), (A, D), (A, E)],就是该无向图的其中一个生成树。其实[(A, E),(A, B), (B, C), (C, D)]也是该无向图的一个生成树。由此可见,一个「无向图」的生成树可以是多个。

https://img-blog.csdnimg.cn/a14684767e51436cb8d2a871f8a8f532.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzUwOTQ1NTA0,size_16,color_FFFFFF,t_70 那么再了解了什么是生成树后,小伙伴们可能又要冒出第二个问题了。什么是最小生成树。最小生成树指的是「加权无向图」中总权重最小的生成树。「图9. 最小生成树」中,所有绿色线条组成的一颗生成树[(A, E),(A, B), (B, C), (C, D)],就是该加权无向图的其中一个最小生成树。其实[(A, E), (E, D), (A, B), (B, C)]也是该加权无向图的另一个最小生成树,由此可见,一个「加权无向图」的最小生成树可以是多个。 https://img-blog.csdnimg.cn/582d461503e64d3183dac8ee51cbc014.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzUwOTQ1NTA0,size_16,color_FFFFFF,t_70 那么在该章节中,我们将学习下「生成最小生成树」的两种算法以及「切分定理」:

  • 切分定理
  • Kruskal 算法
  • Prim 算法

切分定理

「切分」是什么呢?很多的定理都是以人的名字命名的,但是「切分」并不是一个人的名字。在「切分定理」中有两个基本概念,我们需要了解下:

  • 切分:将「图」切成两个部分,称之为一个「切分」。「图 10. 切分图」就是一个「切分」,其中(B, A, E)为一个部分,(C, D)为另外一个部分。
  • 横切边:如果一条边连接的两个顶点属于切分的两个部分,这个边称为「横切边」。在「图10. 切分图」中,(B, C), (A, C), (A, D), (E, D) 均为「横切边」。 https://img-blog.csdnimg.cn/06ceab4eee3d4736825babc45eb8348a.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzUwOTQ1NTA0,size_16,color_FFFFFF,t_70 再了解了切分定理的基础概念之后,我们就需要学习下「切分定理」了。切分定理是 Kruskal 算法和 Prim 算法的重要的理论支撑。那么什么是「切分定理」呢?根据 维基百科 的定义,「切分定理」指的是:

在一幅连通加权无向图中,给定任意的切分,如果有一条横切边的权值严格小于所有其他横切边,则这条边必然属于图的最小生成树中的一条边。

切分定理的证明

视频链接


Kruskal 算法(以边扩散)

「Kruskal 算法」是求解「加权无向图」的「最小生成树」的一种算法。 视频链接

时间复杂度: $O(E*logE)$ $E$ 表示边数。

空间复杂度: $O(V)$ $V$表示顶点数。

练习题–连接所有点的最小费用

https://img-blog.csdnimg.cn/dd0a6038d2a349e9945d92fa3ab9f2cb.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzUwOTQ1NTA0,size_16,color_FFFFFF,t_70 视频讲解

解题代码

class Solution {
    // Kruskal Algorithm
    public int minCostConnectPoints(int[][] points) {
        if (points == null || points.length == 0) {
            return 0;
        }
        int size = points.length;
        PriorityQueue<Edge> pq = new PriorityQueue<Edge>((x, y) -> x.cost - y.cost);
        UnionFind uf = new UnionFind(size);

        for (int i = 0; i < size; i++) {
            for (int j = i+1; j < size; j++) {
                int[] coordinate1 = points[i];
                int[] coordinate2 = points[j];
                // Calculate the distance between two coordinates.
                int cost = Math.abs(coordinate1[0] - coordinate2[0]) + Math.abs(coordinate1[1] - coordinate2[1]);
                Edge edge = new Edge(i, j, cost);
                pq.add(edge);
            }
        }

        int result = 0;
        int count = size - 1;
        while ( pq.size() > 0 && count > 0 ) {
            Edge e = pq.poll();
            if ( !uf.connected(e.point1, e.point2)) {
                uf.union(e.point1, e.point2);
                result += e.cost;
                count--;
            }
        }
        return result;
    }

    class Edge {
        int point1;
        int point2;
        int cost;

        Edge(int point1, int point2, int cost) {
            this.point1 = point1;
            this.point2 = point2;
            this.cost = cost;
        }
    }

    class UnionFind {
        int root[];
        int rank[];

        public UnionFind(int size) {
            root = new int[size];
            rank = new int[size];
            for (int i = 0; i < size; i++) {
                root[i] = i;
                rank[i] = 1; 
            }
        }

        public int find(int x) {
            if (x == root[x]) {
                return x;
            }
            return root[x] = find(root[x]);
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX != rootY) {
                if (rank[rootX] > rank[rootY]) {
                    root[rootY] = rootX;
                } else if (rank[rootX] < rank[rootY]) {
                    root[rootX] = rootY;
                } else {
                    root[rootY] = rootX;
                    rank[rootX] += 1;
                }
            }
        }

        public boolean connected(int x, int y) {
            return find(x) == find(y);
        }
    }
}

Prim算法(以顶点扩散)

「Prim 算法」是求解「加权无向图」的「最小生成树」的另一种算法。 视频链接

算法证明 视频链接

「Kruskal 算法」和 「Prim 算法」区别

在「Kruskal 算法」中,我们通过增加边数来扩大「最小生成树」; 在「Prim 算法」中,我们通过增加顶点来扩大「最小生成树」。

时间复杂度

普通二叉堆:$O(ElogV)O(ElogV)$。

斐波那契堆:$O(E+VlogV)O(E+VlogV)$。

$V$ 表示顶点数,$E$ 表示边数。

空间复杂度

$O(V)$。

$V$表示顶点数。

练习题–连接所有点的最小费用

题目上面有图

视频讲解

解题代码

class Solution {
    // Prim Algorithm
    public int minCostConnectPoints(int[][] points) {
        if (points == null || points.length == 0) {
            return 0;
        }
        int size = points.length;
        PriorityQueue<Edge> pq = new PriorityQueue<Edge>((x, y) -> x.cost - y.cost);
        boolean[] visited = new boolean[size];
        int result = 0;
        int count = size - 1;
        // Add all edges from points[0] vertexs
        for (int j = 1; j < size; j++) {
            // Calculate the distance between two coordinates.
            int[] coordinate1 = points[0];
            int[] coordinate2 = points[j];
            int cost = Math.abs(coordinate1[0] - coordinate2[0]) + Math.abs(coordinate1[1] - coordinate2[1]);
            Edge edge = new Edge(0, j, cost);
            pq.add(edge);
        }
        visited[0] = true;

        while (pq.size() > 0 && count > 0) {
            Edge e = pq.poll();
            int point1 = e.point1;
            int point2 = e.point2;
            int cost = e.cost;
            if ( !visited[point2] ) {
                result += cost;
                visited[point2] = true;
                for (int j = 0; j < size; j++ ) {
                    if ( !visited[j] ) {
                        int distance = Math.abs(points[point2][0] - points[j][0]) + Math.abs(points[point2][1] - points[j][1]);
                        pq.add(new Edge(point2, j, distance));
                    }
                }
                count--;
            }
        }
        return result;
    }

    class Edge {
        int point1;
        int point2;
        int cost;

        Edge(int point1, int point2, int cost) {
            this.point1 = point1;
            this.point2 = point2;
            this.cost = cost;
        }
    }
}