问题描述
请注意,我已经完成了How to detect a loop in a hierarchy of javascript elements 在我们的例子中,我们处理的不是一个链表,而是一个层次图,其中每个节点可能有多个链接到它的子节点。例如,
const graph = {
a: {value: Va,children: {b,d}},b: {value: Vb,children: {c}},c: {value: Vc,children: {a,d,e}}
}
在这个图中,我们应该检测循环 a -> b -> c -> a。
解决方法
如果你只需要确定一个图是否有环,而不需要报告环是什么,你可以用一个简单的递归对图的一个特定节点做这件事,并将它包装在一个调用中测试所有节点。
这是一个实现:
const hasCycle = (graph,name,path = []) =>
path .includes (name)
? true
: (graph?.[name]?.children ?? []) .some (c => hasCycle (graph,c,[...path,name]))
const anyCycles = (graph) =>
Object .keys (graph) .some (k => hasCycle (graph,k))
const graph1 = {a: {value: 1,children: ['b','d']},b: {value: 2,children: ['c']},c: {value: 3,children: ['a','d','e']}}
const graph2 = {a: {value: 1,children: ['d','e']}}
console .log (anyCycles (graph1))
console .log (anyCycles (graph2))
您共享的图形对象中存在一些语法错误,但假设子项是由字符串标识的,您通常会使用深度优先遍历来确定您是否遇到了一个向后引用节点的边已经在当前路径上。
如果发生这种情况,您就有了一个循环,并且可以很容易地从当前路径和反向引用节点推导出循环。
为了避免重复遍历,您还需要跟踪已访问过的节点(无论是否在当前路径上)。无需从已经访问过的节点继续搜索。
要将节点标记为已访问,您可以使用 Set。
function findCycle(graph) {
let visited = new Set;
let result;
// dfs set the result to a cycle when the given node was already on the current path.
// If not on the path,and also not visited,it is marked as such. It then
// iterates the node's children and calls the function recursively.
// If any of those calls returns true,exit with true also
function dfs(node,path) {
if (path.has(node)) {
result = [...path,node]; // convert to array (a Set maintains insertion order)
result.splice(0,result.indexOf(node)); // remove part that precedes the cycle
return true;
}
if (visited.has(node)) return;
path.add(node);
visited.add(node);
if ((graph[node]?.children || []).some(child => dfs(child,path))) return path;
// Backtrack
path.delete(node);
// No cycle found here: return undefined
}
// Perform a DFS traversal for each node (except nodes that get
// visited in the process)
for (let node in graph) {
if (!visited.has(node) && dfs(node,new Set)) return result;
}
}
// Your example graph (with corrections):
const graph = {
a: {value: 1,children: ["b","d"]},children: ["c"]},children: ["a","d","e"]}
};
// Find the cycle
console.log(findCycle(graph)); // ["a","b","c","a"]
// Break the cycle,and run again
graph.c.children.shift(); // drop the edge c->a
console.log(findCycle(graph)); // undefined (i.e. no cycle)