1489. 找到最小生成树里的关键边和伪关键边
题目来源:1489. 找到最小生成树里的关键边和伪关键边 - 力扣(Leetcode)
题目描述
T图论
T最小生成树
T并查集
给你一个 n
个点的带权无向连通图,节点编号为 0
到 n-1
,同时还有一个数组 edges
,其中 edges[i] = [from``i, toi, weighti]
表示在 fromi
和 toi
节点之间有一条带权无向边。最小生成树 (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算法判断当前边是否是关键边/伪关键边。具体做法如下:
- 边按权值从小到大排序
- 获取当前图的最小生成树的权值minVal
- 枚举每条边e
- 删除边e计算最小生成树的权值curVal
- 若图不连通或curVal>minVal,则边e为关键边,goto 3
- (执行到此处,说明存在一棵最小生成树不含边e,接下来只需判断是否存在一棵最小生成树含边e)
- 强制将边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;
}