1. 程式人生 > 實用技巧 >Leetcode 1489找到最小生成樹李關鍵邊和偽關鍵邊

Leetcode 1489找到最小生成樹李關鍵邊和偽關鍵邊

題目定義:

給你一個 n個點的帶權無向連通圖,節點編號為 0到 n-1,同時還有一個數組 edges,
其中 edges[i] = [fromi, toi, weighti]表示在fromi和toi節點之間有一條帶權無向邊
。最小生成樹(MST) 是給定圖中邊的一個子集,它連線了所有節點且沒有環,而且這些邊的權值和最小。
請你找到給定圖中最小生成樹的所有關鍵邊和偽關鍵邊。如果從圖中刪去某條邊,會導致最小生成樹的權值和增加,
那麼我們就說它是一條關鍵邊。偽關鍵邊則是可能會出現在某些最小生成樹中但不會出現在所有最小生成樹中的邊。
請注意,你可以分別以任意順序返回關鍵邊的下標和偽關鍵邊的下標。

示例 1:

輸入:n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
輸出:[[0,1],[2,3,4,5]]
解釋:上圖描述了給定圖。
下圖是所有的最小生成樹。

注意到第 0 條邊和第 1 條邊出現在了所有最小生成樹中,所以它們是關鍵邊,我們將這兩個下標作為輸出的第一個列表。
邊 2,3,4 和 5 是所有 MST 的剩餘邊,所以它們是偽關鍵邊。我們將它們作為輸出的第二個列表。

示例 2 :

輸入:n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
輸出:[[],[0,1,2,3]]
解釋:可以觀察到 4 條邊都有相同的權值,任選它們中的 3 條可以形成一棵 MST 。所以 4 條邊都是偽關鍵邊。

方式一(kruskal):

class Solution {
    public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) {
        int length = edges.length;
        int[][] newEdges = new int[length][4];
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < 3; j++)
                newEdges[i][j] = edges[i][j];
            newEdges[i][3] = i;
        }
        Arrays.sort(newEdges, Comparator.comparingInt((int[] a) -> a[2]));
        //統計最小連線樹的value
        UnionFind unionFind = new UnionFind(n);
        int value = 0;
        for (int i = 0; i < length; i++) {
            if (!unionFind.union(newEdges[i][0], newEdges[i][1]))
                value += newEdges[i][2];
        }
        List<List<Integer>> ans = new ArrayList<List<Integer>>() {{
            add(new ArrayList<>());
            add(new ArrayList<>());
        }};
        //迴圈判斷當前邊是否是關鍵邊 或偽關鍵邊
        //邊有三種(關鍵邊,偽關鍵邊,非關鍵邊)
        for (int i = 0; i < length; i++) {
            UnionFind uf = new UnionFind(n);
            int v = 0;
            //迴圈判斷是否是關鍵邊
            for (int j = 0; j < length; j++) {
                if (i != j && !uf.union(newEdges[j][0], newEdges[j][1])) {
                    v += newEdges[j][2];
                }
            }
            //並查集不連通 或 得出的最小樹權值大於統計最小連線樹的value,則說明是關鍵邊
            if (uf.getCount() != 1 || v > value) {
                ans.get(0).add(newEdges[i][3]);
                continue;
            }
            /*
            * 偽關鍵邊:可能會出現在某些最小生成樹中但不會出現在所有最小生成樹中的邊。
            * 也就是說,我們可以在計算最小生成樹的過程中,最先考慮這條邊,即最先將這條邊的兩個端點在並查集中合併。
            * 設最終得到的最小生成樹權值為 v,如果 v = value,那麼這條邊就是偽關鍵邊
            * */
            //所以需要首先將那條邊放入
            uf = new UnionFind(n);
            uf.union(newEdges[i][0],newEdges[i][1]);
            v = newEdges[i][2];
            for (int j = 0; j < length; j++) {
                if(i != j && !uf.union(newEdges[j][0],newEdges[j][1]))
                    v +=newEdges[j][2];
            }
            if(v == value){
                ans.get(1).add(newEdges[i][3]);
            }
        }
        return ans;
    }
}

class UnionFind {
    private int[] parent;
    private int[] rank;
    private int count;

    UnionFind(int n) {
        this.parent = new int[n];
        this.rank = new int[n];
        count = n;
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    private int find(int index) {
        if (parent[index] != index)
            parent[index] = find(parent[index]);
        return parent[index];
    }

    boolean union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY)
            return true;
        if (rootX < rootY) {
            rootX = rootX ^ rootY;
            rootY = rootX ^ rootY;
            rootX = rootX ^ rootY;
        }
        rank[rootX] += rank[rootY];
        parent[rootY] = rootX;
        count--;
        return false;
    }

    public int getCount() {
        return count;
    }
}

參考:

https://leetcode-cn.com/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/solution/zhao-dao-zui-xiao-sheng-cheng-shu-li-de-gu57q/