【问题标题】:JavaScript: Detect a Loop in a Hierarchical GraphJavaScript:检测层次图中的循环
【发布时间】:2021-02-06 04:57:19
【问题描述】:

请注意,我已经通过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。

【问题讨论】:

  • 那么你的问题在哪里?请向我们展示代码,并解释您尝试实现的which algorithm
  • @Bergi 这是您链接到的维基百科文章的引述:“从理论上讲,人们可以通过构造函数图(即每个顶点具有的有向图)来查看相同的问题图单个出边)其顶点是 S 的元素,其边将元素映射到相应的函数值。”正如我在问题中解释的那样,我们正在处理一个有向图,其中每个顶点都有多个出边。
  • 那句话是指“从输入图构建功能图”——它根本不限制输入图的形状。

标签: javascript algorithm hierarchy directed-graph network-analysis


【解决方案1】:

如果您只需要确定一个图是否有循环,而不需要报告循环是什么,您可以通过简单的递归对图的特定节点执行此操作,并将其包装在一个调用中测试所有节点。

这是一个实现:

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: ['b', 'd']}, b: {value: 2, children: ['c']}, c: {value: 3, children: ['d', 'e']}}

console .log (anyCycles (graph1))
console .log (anyCycles (graph2))

【讨论】:

  • 我希望我可以将两个答案都标记为正确。我最终在程序的不同部分使用了这两个答案。谢谢。
【解决方案2】:

您共享的图形对象中存在一些语法错误,但假设子对象是由字符串标识的,您通常会使用深度优先遍历来确定您是否碰到作为对节点的反向引用的边已经在当前路径上。

如果发生这种情况,您就有了一个循环,并且该循环可以很容易地从当前路径和反向引用的节点导出。

为了避免重复遍历,您还需要跟踪已访问的节点(无论是否在当前路径上)。无需从已访问过的节点继续搜索。

要将节点标记为已访问,您可以使用 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"]},
   b: {value: 2, children: ["c"]},
   c: {value: 3, 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)

【讨论】:

  • 很高兴您找到了答案。我只是想确保这个答案没有问题,因为今天你删除了接受标记。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多