Leetcode刷题笔记 1489. 找到最小生成树里的关键边和伪关键边

1489. 找到最小生成树里的关键边和伪关键边

知识点:最小生成树、并查集
时间:2021年1月21日
题目链接

题目
给你一个 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) 数对都是互不相同的。

解题思路

  1. 这是一道最小生成树 + 并查集的题 需要用到 kruskal 可以参考 Leetcode刷题笔记 1584. 连接所有点的最小费用
  2. 这道题里的关键是如何找到关键边 和 伪关键边
  3. 关键边 : 如果删去这条边 构不成连通图 或者生成的最小树的值 大于 原来最小生成树的值
  4. 伪关键边: 可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边 先把这条边放入 就是确定要这条边 最终得到的最小生成树的值 等于原来最小的值
  5. 这样清楚了 如何定义这两种边的时候 把下标也放入边中 防止排序对下标造成影响
  6. 再按照经典的kruskal得到最小生成树
  7. 遍历每一条边 如果不放入 是否是关键边 最先放入判断 是否是伪关键边
  8. 注意 关键边满足伪关键边的性质 先要判断关键边

代码

#include "cheader.h"
class Solution {
public:
    struct UN{
        vector<int> ancestor;
        UN(int x){
            ancestor.resize(x);
            for(int i = 0; i < x;i++)
                ancestor[i] = i;
        }
        int find(int x){
            if(ancestor[x] == x)
                return x;
            else
                return ancestor[x] = find(ancestor[x]);
        }
        void merge(int x,int y){
            ancestor[find(x)] = ancestor[find(y)];
        }
    };
    static bool cmp(vector<int> x,vector<int> y){
        return x[2] < y[2];
    }
    vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
        //每条边最后放入下标 为了避免排序对下标的影响 最后输出方便
        int m = edges.size();
        for(int i = 0; i < m; i++)
            edges[i].push_back(i);
        
        int minv = 0;
        UN un = UN(n);
        // 经典kruskal解法
        sort(edges.begin(),edges.end(),cmp);
        for(int i = 0; i < m;i++){
            int x = edges[i][0],y = edges[i][1],d = edges[i][2];
            if(un.find(x) != un.find(y)){
                minv += d;
                un.merge(x, y);
            }
        }
        
        un = UN(n);
        vector<vector<int>> ans(2);
        for(int i = 0; i < m; i++){
            // 判断i是否是关键边
            un = UN(n);
            int v = 0; //去除 第i条边的 最小生成树的答案
            int cn = 0;//连通的节点数
            for(int j = 0; j < m; j++){
                // 不放入第i条边
                if(j != i){
                    int x = edges[j][0],y = edges[j][1],d = edges[j][2];
                    if(un.find(x) != un.find(y)){
                        v += d;
                        cn ++;
                        un.merge(x, y);
                    }
                }
            }
            if(cn != n-1 || (cn == n-1 && v > minv)){ //如果去除了第i条边 不能构成连通图
                ans[0].push_back(edges[i][3]);      //或者所构成的生成树 大于最小生成树
                continue;
            }
            
            //判断i是否是伪关键边
            un = UN(n);
            int x = edges[i][0],y = edges[i][1],d = edges[i][2];
            un.merge(x, y); //直接加入这条边
            v = d;
            for(int j = 0; j < m; j++){
                if(j != i){
                    int x = edges[j][0],y = edges[j][1],d = edges[j][2];
                    if(un.find(x) != un.find(y)){
                        v += d;
                        un.merge(x, y);
                    }
                }
            }
            if(v == minv){
                ans[1].push_back(edges[i][3]);
            }
        }
        return ans;
    }
};
int main()
{
    int n = 5;
    vector<vector<int>> edges;
    edges.push_back({0,1,1});edges.push_back({1,2,1});edges.push_back({2,3,2});
    edges.push_back({0,3,2});edges.push_back({0,4,3});edges.push_back({3,4,3});
    edges.push_back({1,4,6});
    Solution s;
    vector<vector<int>> ans = s.findCriticalAndPseudoCriticalEdges(n, edges);
    for(int x :ans[0])
        cout<<x<<endl;
    cout<<"---------------"<<endl;
    for(int x :ans[1])
        cout<<x<<endl;
    return 0;
}

今天也是爱zz的一天哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值