【每日一题】找到最小生成树里的关键边和伪关键边

文章介绍了如何通过Kruskal算法和并查集来找出图中最小生成树的所有关键边和伪关键边。首先,按边权值排序,然后计算最小生成树的权值。接着,枚举每条边,判断删除该边是否影响最小生成树的权值,从而确定边是否为关键边或伪关键边。
摘要由CSDN通过智能技术生成

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

题目来源:1489. 找到最小生成树里的关键边和伪关键边 - 力扣(Leetcode)

题目描述
T图论
T最小生成树
T并查集

给你一个 n 个点的带权无向连通图,节点编号为 0n-1 ,同时还有一个数组 edges ,其中 edges[i] = [from``i, toi, weighti] 表示在 fromitoi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。

请你找到给定图中最小生成树的所有关键边和伪关键边。如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。

请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。

输入
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]]

输入
n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
输出
[[],[0,1,2,3]]
数据范围
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) 数对都是互不相同的。
问题分析
  • 最小生成树+稀疏图 => Kruskal算法

  • maxN=100,maxM=200 => 枚举

  • 0 <= fromi < toi < n,所有 (fromi, toi) 数对都是互不相同的 => 无重边,无自环

  • maxW=1000,maxN=100 => int不会溢出

于是,总体思路就是,枚举每条边,通过Kruskal算法判断当前边是否是关键边/伪关键边。具体做法如下:

  1. 边按权值从小到大排序
  2. 获取当前图的最小生成树的权值minVal
  3. 枚举每条边e
  4. 删除边e计算最小生成树的权值curVal
    • 若图不连通或curVal>minVal,则边e为关键边,goto 3
  5. (执行到此处,说明存在一棵最小生成树不含边e,接下来只需判断是否存在一棵最小生成树含边e)
  6. 强制将边e纳入最小生成树,计算当前图的最小生成树的权值curVal
    • 若curVal=minVal,则边e为伪关键边

注意两点:

  • 关键边不是伪关键边:是关键边说明不存在一棵最小生成树不含当前边
  • 不是关键边+存在一棵最小生成树含当前边:不是关键边说明存在一棵最小生成树不含当前边

时间复杂度:O(m2×α(n))

空间复杂度:O(m+n)

代码实现
const int N = 1e2 + 5;
// 并查集:父结点、连通块大小、连通块数目
int p[N], size[N], cnt;

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

bool unite(int x, int y) {
    x = find(x), y = find(y);
    if (x == y)return false;
    if (size[x] < size[y])swap(x, y);
    p[y] = x, size[x] += size[y], cnt--;
    return true;
}

void initUnionFind(int n) {
    cnt = n;
    for (int i = 0; i < n; i++)p[i] = i, size[i] = 1;
}


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);
    // 按边权从小到大排序
    sort(edges.begin(), edges.end(),
         [](auto &u, auto &v) { return u[2] < v[2]; }
    );

    // 计算最小生成树的权值
    int minVal = 0;
    initUnionFind(n);
    for (int i = 0; i < m; i++) {
        if (unite(edges[i][0], edges[i][1]))
            minVal += edges[i][2];
    }

    // 枚举每条边
    vector<vector<int>> ans(2);
    for (int i = 0; i < m; i++) {
        initUnionFind(n);

        // 判断是否是关键边
        int curVal = 0;
        for (int j = 0; j < m; j++) {
            if (i != j && unite(edges[j][0], edges[j][1]))
                curVal += edges[j][2];
        }
        if (cnt != 1 || curVal > minVal) {
            ans[0].push_back(edges[i][3]);
            continue;
        }

        // 若执行到此处,说明当前边不是关键边
        // 也即删除当前边会不会使得生成树权值增大,也即生成树权值不变
        // 也即,存在一棵最小生成树不含当前边
        // 若又存在一棵最小生成树含当前边,则可断言当前边是伪关键边

        // 判断是否是伪关键边(是否存在一棵最小生成树含当前边)
        initUnionFind(n);
        unite(edges[i][0], edges[i][1]), curVal = edges[i][2];  // 加入当前边
        for (int j = 0; j < m; j++) {
            if (i != j && unite(edges[j][0], edges[j][1]))
                curVal += edges[j][2];
        }
        if (curVal == minVal)ans[1].push_back(edges[i][3]);
    }

    return ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值