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

找到最小生成樹裡的關鍵邊和偽關鍵邊

難度:困難
給你一個 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 條邊都是偽關鍵邊。

提示:

2 <= n <= 100
1 <= edges.length <= min(200, n * (n - 1) / 2)
edges[i].length == 3
0 <= fromi < toi < n
1 <= weighti<= 1000
所有 (fromi, toi)數對都是互不相同的。

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree

最小生成樹 + Kruskal

class UF{
public:
    vector<int> par, rank;
    int connectCount;

public:
    UF(int _n) : connectCount(_n), par(_n), rank(_n){
        iota(par.begin(), par.end(), 0);
    }

    int find(int x){
        while(x != par[x])
            x = par[x];
        return x;
    }

    bool insert(int x, int y){
        int px = find(x);
        int py = find(y);
        if(px == py)
            return false;
        if(rank[px] < rank[py])
            swap(px, py);
        else if(rank[px] == rank[py])
            rank[px]++;
        par[py] = px;
        --connectCount;
        return true;
    }


};

class Solution {
public:
    vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
        int size = edges.size();
        for(int i = 0; i < size; ++i)
            edges[i].push_back(i);
        sort(edges.begin(), edges.end(), [](auto& iter1, auto& iter2){
            return iter1[2] < iter2[2];
        });
        int value = 0;
        UF uf_std(n);
        for(auto iter : edges){
            if(uf_std.insert(iter[0], iter[1]))
                value += iter[2];
        }
        vector<vector<int>> res(2);
        for(int i = 0; i < size; ++i){
            UF uf(n);
            int v = 0;
            // 關鍵邊:除去這個邊
            // 1、無法構成最小生成樹
            // 2、最小生成樹的value大於有這個邊的最小生成樹
            for(int j = 0; j < size; ++j){
                if(j != i && uf.insert(edges[j][0], edges[j][1]))
                    v += edges[j][2];
            }
            if(uf.connectCount != 1 || (uf.connectCount == 1 && v > value)){
                res[0].push_back(edges[i][3]);
                continue;
            }
            // 偽關鍵邊 --- 在一些最小生成樹中包含這個邊,在某些最小生成樹中不包含這個邊
            // 把這個邊作為第一個邊加入到最小生成樹中,最後的結果value不變的話,那麼這個邊就是偽關鍵邊
            uf = UF(n);
            uf.insert(edges[i][0], edges[i][1]);
            v = edges[i][2];
            for(int j = 0; j < size; ++j){
                if(j != i && uf.insert(edges[j][0], edges[j][1]))
                    v += edges[j][2];
            }
            if(uf.connectCount == 1 && v == value)
                res[1].push_back(edges[i][3]);
        }
        return res;
    }
};