中等
依赖关系检测与版本更新
图拓扑排序判断环
题目描述
爱丽丝在人偶制作过程中需要管理部件之间的依赖关系。每组数据包含若干条依赖关系,每条表示“人偶 u 依赖于部件 v,要求的版本号为 version”。
需要完成两件事:
- 检测依赖关系中是否存在循环依赖(有向图中的环)
- 如果无环,对于每个被依赖的部件 v,找出所有依赖于它的条目中的最大版本号,并按输入顺序输出更新后的依赖关系
数据范围:
- 编号 u, v 为正整数,且 1 ≤ u, v ≤ 10⁹
- 版本号 1 ≤ version ≤ 99
- 在同一组数据中,同一个依赖序对 (u, v) 最多出现一次
- 依赖关系数量 0 < n < 100
示例
输入:
3
1,2,23
2,3,34
4,2,25
3
1,2,23
2,3,34
3,1,12
输出:
1,2,25
2,3,34
4,2,25
false
说明:
第一组:人偶 1 依赖 2(版本 23),人偶 4 也依赖 2(版本 25),因此部件 2 的最大需求版本为 25;人偶 2 依赖 3(版本 34),因此部件 3 的最大需求版本为 34。网络中不存在环。
第二组:1 依赖 2,2 依赖 3,3 依赖 1,构成闭环,输出 false。
解题思路
题目分为两个子问题:
1. 检测有向图环
方法一:拓扑排序(Kahn 算法)
- 统计每个节点的入度
- 将所有入度为 0 的节点入队
- 每次出队一个节点,将其所有邻居的入度减 1,若邻居入度变 0 则入队
- 最终:处理的节点数 < 总节点数 → 有环
方法二:DFS 三色标记法
- 白色(0):未访问
- 灰色(1):正在访问中(在递归栈中)
- 黑色(2):已完成访问
- DFS 时遇到灰色节点即发现环
2. 更新版本号
遍历所有依赖关系,记录每个被依赖节点 v 的最大版本号;然后按输入顺序用最大值替换原版本号。
代码实现
/**
* 拓扑排序(Kahn 算法)检测有向图是否有环
* 时间复杂度:O(V + E)
*/
function isLoop(list) {
const indegree = {};
const graph = {};
for (const [u, v] of list) {
if (!(u in indegree)) indegree[u] = 0;
if (!(v in indegree)) indegree[v] = 0;
if (!graph[u]) graph[u] = [];
if (!graph[v]) graph[v] = [];
}
for (const [u, v] of list) {
graph[v].push(u); // v 指向 u(被依赖者 → 依赖者)
indegree[u] = (indegree[u] || 0) + 1;
}
const queue = [];
const allNodes = Object.keys(indegree);
for (const node of allNodes) {
if (indegree[node] === 0) queue.push(node);
}
let processed = 0;
while (queue.length > 0) {
const u = queue.shift();
processed++;
for (const next of graph[u] || []) {
indegree[next]--;
if (indegree[next] === 0) queue.push(next);
}
}
return processed < allNodes.length;
}
/**
* DFS 三色标记法检测有向图是否有环
* 时间复杂度:O(V + E),空间复杂度:O(V)
*/
function isLoopDFS(list) {
const graph = {};
const color = {}; // 0=白 1=灰 2=黑
for (const [u, v] of list) {
if (!graph[v]) graph[v] = [];
graph[v].push(u);
color[u] = 0;
color[v] = 0;
}
function dfs(node) {
color[node] = 1;
for (const next of graph[node] || []) {
if (color[next] === 1) return true; // 遇到灰色 → 有环
if (color[next] === 0 && dfs(next)) return true;
}
color[node] = 2;
return false;
}
for (const node of Object.keys(color)) {
if (color[node] === 0 && dfs(node)) return true;
}
return false;
}
/**
* 更新版本号:对每个被依赖节点 v,取所有入边的最大 version
*/
function getFixedlist(list) {
const maxVersionMap = {};
for (const [main, pre, version] of list) {
maxVersionMap[pre] = Math.max(version, maxVersionMap[pre] || 0);
}
return list.map(([main, pre]) => [main, pre, maxVersionMap[pre]]);
}
复杂度分析
- 时间复杂度: O(V + E),建图和拓扑排序/DFS 都是线性时间。n < 100,非常快。
- 空间复杂度: O(V + E),存储邻接表和入度表。
示例输入 / 输出
下面给出一组输入输出示例,便于对照题意与结果。
编号 dependency-detection难度 中等
输入
3 1,2,23 2,3,34 4,2,25 3 1,2,23 2,3,34 3,1,12
输出
1,2,25 2,3,34 4,2,25 false