最小生成树问题

最小生成树问题

简介

最小生成树是一副连通加权无向图中一棵权值最小的生成树。

最小生成树是图论算法中比较经典的问题,在现实生活中也有非常多的应用。有两种比较经典的算法,都是使用了贪心的思想解决:

  • Prim算法(普利姆算法)

    Prim算法的每一步都会为一棵生长中的树添加一条边,该树最开始只有一个顶点,然后会添加{V-1}个边。每次总是添加生长中的树和树中除该生长的树以外的部分形成的切分的具有最小权值的横切边。Prim算法的时间复杂度为{O(E + VlogV)}。

  • Kruskal算法(克鲁斯克尔算法)

    按照边的权重顺序(从小到大)将边加入生成树中,但是若加入该边会与生成树形成环则不加入该边。直到树中含有{V-1}条边为止。这些边组成的就是该图的最小生成树。Kruskal算法的时间复杂度为{ElogE}

    值得注意的是,在判断生成树是否有环时使用了并查集这样的数据结构

算法实现

Prim

class Solution {
    public int minCostConnectPoints(int[][] points) {
        return prim(points);
    }

    /**
     * 图论中的最小生成树Prim算法
     * @param points
     * @return
     */
    public int prim(int[][] points){
        int res = 0;
        
        //1.构造邻接矩阵
        int len = points.length;
        int[][] dp = new int[len][len]; // dp[0][1] 表示下标为0 到 下标为1 的 曼哈顿值
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                if(i == j){
                    dp[i][j] = 0;
                }else {
                    dp[i][j] = computeConst(points[i][0],points[i][1],points[j][0],points[j][1]);
                    dp[j][i] = computeConst(points[i][0],points[i][1],points[j][0],points[j][1]);
                }
            }
        }
        // v_new    表示图中节点的访问情况,最开始全部为-1,表示未加入到v_new中,若某节点加入到了v_new中, 则将其置为0
        int[] v_new = new int[len];
        // lowcost  保存每个节点离v_new中所有节点的最短距离。初始化为Integer.MAX_VALUE,如果节点已经加入到了v_new中,则置为-1
        int[] lowcost = new int[len];

        for (int i = 0; i < len; i++) {
            v_new[i] = -1;
            lowcost[i] = Integer.MAX_VALUE;
        }

        //2.随机选取一个节点,默认为第一个节点,并且更新lowcost里面的值
        v_new[0] = 0;
        for (int i = 0; i < len; i++) {
            if(i == 0){
                continue;
            }else {
                lowcost[i] = dp[0][i];
            }
        }

        //3. 遍历未放入v_new 的剩余的节点,
        for (int i = 1; i < len; i++) {
            // 找到图中离 v_new 最近的点
            int minIdx = -1; // minIdx 表示找到节点的下标
            int minVal = Integer.MAX_VALUE; // minVal 表示找到节点的下标对应的值
            
            for (int j = 0; j < len; j++) {
                if(lowcost[j] < minVal){
                    minIdx = j;
                    minVal = lowcost[j];
                }
            }

            //更新 v_new 里面的值
            res += minVal;
            v_new[minIdx] = 0;
            lowcost[minIdx] = Integer.MAX_VALUE;

            //更新 lowcost 里面的值
            for (int j = 0; j < len; j++) {
                if (v_new[j] == -1 && dp[j][minIdx] < lowcost[j]){
                    lowcost[j] =  dp[j][minIdx];
                }
            }
        }
        return res;
    }

    private int computeConst(int x1, int y1, int x2,int y2){
        return Math.abs(x1 - x2) + Math.abs(y1 - y2);
    }
}

Kruskal

class Solution {
    // Kruskal
    public int minCostConnectPoints(int[][] points) {
        List<Edge> edges = new ArrayList<>();
    
        for(int i = 0; i < points.length; i++) {
            for(int j = i; j < points.length; j++) {
                edges.add(new Edge(i, j, getLength(points, i, j)));
            } 
        }

        Collections.sort(edges, (a, b) -> (
            a.len - b.len
        ));

        int ans = 0, edgeNum = 0;
        DSU dsu = new DSU(points.length);
        for(Edge edge : edges) {
            if(dsu.union(edge.point1, edge.point2)){
                ans += edge.len;
                edgeNum++;                
            }
            if(edgeNum == points.length - 1) break;
        }

        return ans;
    }


    public int getLength(int[][] points, int point1, int point2) {
        return Math.abs(points[point1][0] - points[point2][0]) 
             + Math.abs(points[point1][1] - points[point2][1]);
    }


    private class Edge{
        int point1;
        int point2;
        int len;

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

    }

    private class DSU {
        int[] parents, rank;

        public DSU(int N) {
            parents = new int[N];
            rank = new int[N];
            for(int i = 0; i < N; i++) {
                parents[i] = i;
                rank[i] = 1;
            }
        }

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

        public boolean union(int x, int y) {
            int xr = find(x);
            int yr = find(y);
            if(xr == yr) return false;
            if(rank[xr] > rank[yr]) {
                parents[yr] = xr;
            } else if(rank[xr] < rank[yr]) {
                parents[xr] = yr;
            } else {
                parents[xr] = yr;
                rank[yr]++;
            }
            return true;
        }
    }
}

相关题目

Leetcode1584-连接所有点的最小费用

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值