为 Kruskal 算法实现联合查找算法以在 Java 中查找最小生成树

Posted

技术标签:

【中文标题】为 Kruskal 算法实现联合查找算法以在 Java 中查找最小生成树【英文标题】:Implementing Union-Find Algorithm for Kruskal's Algorithm to find Minimum Spanning Tree in Java 【发布时间】:2021-10-06 23:47:10 【问题描述】:

我正在尝试解决以下 Leetcode 问题 (https://leetcode.com/problems/connecting-cities-with-minimum-cost),我的方法是使用 Kruskal 算法使用 Union-Find 数据结构从输入图中计算出最小生成树 (MST) 的总权重.但是,我的代码在线通过了51/63的测试用例,在下面的测试用例上返回了错误的结果,这太难调试了,因为输入图太大了。

50
[[2,1,22135],[3,1,13746],[4,3,37060],[5,2,48513],[6,3,49607],[7,1,97197],[8,2,95909],[9,2,82668],[10,2,48372],[11,4,17775],[12,2,6017],[13,1,51409],[14,2,12884],[15,7,98902],[16,14,52361],[17,8,11588],[18,12,86814],[19,17,49581],[20,4,41808],[21,11,77039],[22,10,80279],[23,16,61659],[24,12,89390],[25,24,10042],[26,12,78278],[27,15,30756],[28,6,2883],[29,8,3478],[30,7,29321],[31,12,47542],[32,20,35806],[33,3,26531],[34,12,16321],[35,27,82484],[36,7,55920],[37,24,21253],[38,23,90537],[39,7,83795],[40,36,70353],[41,34,76983],[42,14,63416],[43,15,39590],[44,9,86794],[45,3,31968],[46,19,32695],[47,17,40287],[48,1,27993],[49,12,86349],[50,11,52080],[17,27,65829],[42,45,87517],[14,23,96130],[5,50,3601],[10,17,2017],[26,44,4118],[26,29,93146],[1,9,56934],[22,43,5984],[3,22,13404],[13,28,66475],[11,14,93296],[16,44,71637],[7,37,88398],[7,29,56056],[2,34,79170],[40,44,55496],[35,46,14494],[32,34,25143],[28,36,59961],[10,49,58317],[8,38,33783],[8,28,19762],[34,41,69590],[27,37,26831],[15,23,53060],[5,11,7570],[20,42,98814],[18,34,96014],[13,43,94702],[1,46,18873],[44,45,43666],[22,40,69729],[4,25,28548],[8,46,19305],[15,22,39749],[33,48,43826],[14,15,38867],[13,22,56073],[3,46,51377],[13,15,73530],[6,36,67511],[27,38,76774],[6,21,21673],[28,49,72219],[40,50,9568],[31,37,66173],[14,29,93641],[4,40,87301],[18,46,41318],[2,8,25717],[1,7,3006],[9,22,85003],[14,45,33961],[18,28,56248],[1,31,10007],[3,24,23971],[6,28,24448],[35,39,87474],[10,50,3371],[7,18,26351],[19,41,86238],[3,8,73207],[11,34,75438],[3,47,35394],[27,32,69991],[6,40,87955],[2,18,85693],[5,37,50456],[8,20,59182],[16,38,58363],[9,39,58494],[39,43,73017],[10,15,88526],[16,23,48361],[4,28,59995],[2,3,66426],[6,17,29387],[15,38,80738],[12,43,63014],[9,11,90635],[12,20,36051],[13,25,1515],[32,40,72665],[10,40,85644],[13,40,70642],[12,24,88771],[14,46,79583],[30,49,45432],[21,34,95097],[25,48,96934],[2,35,79611],[9,26,71147],[11,37,57109],[35,36,67266],[42,43,15913],[3,30,44704],[4,32,46266],[5,10,94508],[31,39,45742],[12,25,56618],[10,45,79396],[15,28,78005],[19,32,94010],[36,46,4417],[6,35,7762],[10,13,12161],[49,50,60013],[20,23,6891],[9,50,63893],[35,43,74832],[10,24,3562],[6,8,47831],[29,32,82689],[7,47,71961],[14,41,82402],[20,33,38732],[16,26,24131],[17,34,96267],[21,46,81067],[19,47,41426],[13,24,68768],[1,25,78243],[2,27,77645],[11,25,96335],[31,45,30726],[43,44,34801],[3,42,22953],[12,23,34898],[37,43,32324],[18,44,18539],[8,13,59737],[28,37,67994],[13,14,25013],[22,41,25671],[1,6,57657],[8,11,83932],[42,48,24122],[4,15,851],[9,29,70508],[7,32,53629],[3,4,34945],[2,32,64478],[7,30,75022],[14,19,55721],[20,22,84838],[22,25,6103],[8,49,11497],[11,32,22278],[35,44,56616],[12,49,18681],[18,43,56358],[24,43,13360],[24,47,59846],[28,43,36311],[17,25,63309],[1,14,30207],[39,48,22241],[13,26,94146],[4,33,62994],[40,48,32450],[8,19,8063],[20,29,56772],[10,27,21224],[24,30,40328],[44,46,48426],[22,45,39752],[6,43,96892],[2,30,73566],[26,36,43360],[34,36,51956],[18,20,5710],[7,22,72496],[3,39,9207],[15,30,39474],[11,35,82661],[12,50,84860],[14,26,25992],[16,39,33166],[25,41,11721],[19,40,68623],[27,28,98119],[19,43,3644],[8,16,84611],[33,42,52972],[29,36,60307],[9,36,44224],[9,48,89857],[25,26,21705],[29,33,12562],[5,34,32209],[9,16,26285],[22,37,80956],[18,35,51968],[37,49,36399],[18,42,37774],[1,30,24687],[23,43,55470],[6,47,69677],[21,39,6826],[15,24,38561]]

我无法理解为什么我的代码会通过测试用例失败,因为我相信我正在正确地实施 Kruskal 算法的步骤:

    按重量递增的顺序对连接进行排序。 通过遍历排序列表中的每个连接并选择该连接(如果它不会导致 MST 中的循环)来构建 MST。

下面是我的 Java 代码:

class UnionFind 
    // parents[i] = parent node of node i.
    // If a node is the root node of a component, we define its parent
    // to be itself.
    int[] parents;
    
    public UnionFind(int n) 
        this.parents = new int[n];
        
        for (int i = 0; i < n; i++) 
            this.parents[i] = i;
        
    
    
    // Merges two nodes into the same component.
    public void union(int node1, int node2) 
        int node1Component = find(node1);
        int node2Component = find(node2);
        
        this.parents[node1Component] = node2Component;
    
    
    // Returns the component that a node is in.
    public int find(int node) 
        while (this.parents[node] != node) 
            node = this.parents[node];
        
        
        return node;
    


class Solution 
    public int minimumCost(int n, int[][] connections) 
        UnionFind uf = new UnionFind(n + 1);
        
        // Sort edges by increasing cost.
        Arrays.sort(connections, new Comparator<int[]>() 
            @Override
            public int compare(final int[] a1, final int[] a2) 
                return a1[2] - a2[2];
            
        );
        
        int edgeCount = 0;
        int connectionIndex = 0;
        int weight = 0;
        
        // Greedy algorithm: Choose the edge with the smallest weight 
        // which does not form a cycle. We know that an edge between 
        // two nodes will result in a cycle if those nodes are already 
        // in the same component.
        for (int i = 0; i < connections.length; i++) 
            int[] connection = connections[i];
            int nodeAComponent = uf.find(connection[0]);
            int nodeBComponent = uf.find(connection[1]);
            
            if (nodeAComponent != nodeBComponent) 
                weight += connection[2];
                edgeCount++;
            
            
            if (edgeCount == n - 1) 
                break;
            
        
        
        // MST, by definition, must have (n - 1) edges.
        if (edgeCount == n - 1) 
            return weight;
        
        return -1;
    

【问题讨论】:

能否请您发布完整的问题文本。此问题已锁定高级访问权限 在您的代码中,您没有在 weight += connection[2]; edgeCount++; 之后合并集合。我认为可能是这种情况 2 个问题:1) union 永远不会被调用。 2) 当a1[2] - a2[2] 溢出时,您的比较器返回错误的结果。 【参考方案1】:

正如@geobreze 所说,我忘记将节点 A 和节点 B 的组件(不相交集)联合起来。以下是更正后的代码:

if (nodeAComponent != nodeBComponent) 
    uf.union(nodeAComponent, nodeBComponent);
    weight += connection[2];
    edgeCount++;

【讨论】:

以上是关于为 Kruskal 算法实现联合查找算法以在 Java 中查找最小生成树的主要内容,如果未能解决你的问题,请参考以下文章

查找最小生成树:克鲁斯克尔算法(Kruskal)算法

[Python]贪心算法-Prim-和-Kruskal实现-最小生成树

贪心算法-最小生成树Kruskal算法和Prim算法

技术图文:如何利用C# 实现 Kruskal 最小生成树算法?

最小生成树Kruskal算法

(最小生成树)Kruskal算法