【问题标题】:Recursive "hasCycle" function continues "running" even when a basecase is met即使满足基本情况,递归“hasCycle”函数也会继续“运行”
【发布时间】:2021-05-13 12:53:45
【问题描述】:

假设我得到了pairs 的array(其中pair[0] 取决于pair[1])。我想检测任何对依赖项之间是否存在循环。

周期: [[0,1], [1,2], [2, 1]]

说明:1 -> 2和2 -> 1之间有一个循环

不是循环: [[0,1], [1,2], [0, 2]]

TLDR;

我遇到的问题是......一旦我“检测到”了一个循环,我似乎无法弄清楚如何“返回”它。调用堆栈继续执行“其他”子级,但我希望它停止。

您可以跳到底部(算法)

方法:

  1. 使用Map ✅ 创建对的图形表示
/**
 * @param { array } [ [intA, intB] ] - Nested array of integers pairs
 * @return { Map } (representing a Graph)
 */
function createGraph(array) {
    const nodes = new Map();
    
    // Create Verticies
    array.map(pair => {
        const aClass = pair[0];
        const bClass = pair[1];
        nodes.set(aClass, []);
        nodes.set(bClass, []);
    })
    
    // Create Edges
    array.map(pair => {
        const aClass = pair[0];
        const bClass = pair[1];
        nodes.get(aClass).push(bClass);
    });
    return nodes;
}

到目前为止,它按预期工作:

// Create Graph
const array = [[0,1], [1,2], [0, 2]];
const graph = createGraph(array);

console.log(graph); 
// Map(3) { 0 => [ 1, 2 ], 1 => [ 2 ], 2 => [] }

  1. 对所有未访问的节点(在GreySet 中)进行 DFS(直到我们检测到一个循环)。 ⚠️

我使用了 3 Colored Set 方法。我真的很喜欢算法并且真的希望实现它。 据我了解如下。

  • WhiteSet 包含所有未被触及的节点。
  • GreySet 包含当前正在探索的节点。因此,在我们的 DFS 中,“父”将保留在该集合中。
  • BlackSet 将保存已经探索过的节点(直到没有循环的地步)。

所以...如果我们探索BlackSet 中的一个孩子,我们没有理由进一步探索它(它已经探索过了,而且它没有任何循环)。 但是,如果我们遇到一个不在 WhiteSet 中的子节点并且它存在于 GreySet 中,这意味着我们有一个循环。

这是我的代码,我在下面添加了console.logs。 我遇到的问题是......一旦我“检测到”了一个循环,我似乎无法弄清楚如何“返回”它。它会继续执行,如您所见。

算法:

// Create Graph
const graph = createGraph(array);
console.log(graph); // Looks Good

// Detect Cycle

// Create 3 Sets
const whtSet = new Set(graph.keys()); // Put all the Integer "node values" the set.
const grySet = new Set();
const blkSet = new Set();

const unvisitedValues = whtSet.keys(); // Iterator

while (whtSet.size > 0) {
  const doesItHaveCycle = hasCycle(unvisitedValues.next().value); // Expore any unexplored nodeVal

  console.log('whtSet', whtSet);
  console.log('grySet', grySet);
  console.log('blkSet', blkSet);

  console.log('does it have a cycle', doesItHaveCycle);
}

function hasCycle(nodeVal) {
  // Blackset means it has been compltely explored :)
  if (blkSet.has(nodeVal)) return false;

  // This means we have found a cycle
  if (!whtSet.has(nodeVal) && grySet.has(nodeVal)) return true;

  // Remove it from the whiteSet, into the greySet.
  whtSet.delete(nodeVal);
  grySet.add(nodeVal);

  // Recurse Children
  graph.get(nodeVal).forEach((child) => {
    let doesHaveCycle = hasCycle(child);
    console.log(
      'doesHaveCycle result: ',
      doesHaveCycle,
      'when exploring nodeVal',
      nodeVal,
      'and child',
      child
    );
    if (doesHaveCycle === true) return true; // RETURN THIS PLS lol
  });
  // If above was true, I DO NOT want it to come here, but it still does.
  console.log('if above was true... shouldnt come here');

  // Now, that we have explored all of the children, remove it from Grey Set...
  // Add it to the Black Set
  grySet.delete(nodeVal);
  blkSet.add(nodeVal);
  return false; // IDK
}

console.log的结果

doesHaveCycle result:  true when exploring nodeVal 2 and child 1
if above was true... shouldnt come here
doesHaveCycle result:  false when exploring nodeVal 1 and child 2
if above was true... shouldnt come here
doesHaveCycle result:  false when exploring nodeVal 0 and child 1
if above was true... shouldnt come here
whtSet Set(0) {}
grySet Set(0) {}
blkSet Set(3) { 2, 1, 0 }
does it have a cycle false

我 100% 开放修改代码,现在一团糟。但是,我还是想用“3 Color Set”的方式。

关于更多上下文,我试图解决这个名为Course Schedule 的可爱算法问题。

【问题讨论】:

    标签: javascript algorithm recursion set


    【解决方案1】:
      // Recurse Children
      graph.get(nodeVal).forEach((child) => {
        let doesHaveCycle = hasCycle(child);
        console.log(
          'doesHaveCycle result: ',
          doesHaveCycle,
          'when exploring nodeVal',
          nodeVal,
          'and child',
          child
        );
        if (doesHaveCycle === true) return true; // RETURN THIS PLS lol
      });
    

    “返回此 PLS 大声笑”命令通常有效,但这是一种特殊情况。 foreach 只是在每个元素上调用回调,而忽略回调的返回值。

    你需要的函数叫做some。它像foreach 一样在每个元素上调用给定函数,并在其中一个调用返回true 时立即返回true。 (它基本上告诉 some 元素的回调是否为 true。)否则返回 false。

    some 是这样使用的:

      // Recurse Children
      const cycle = graph.get(nodeVal).some((child) => {
        let doesHaveCycle = hasCycle(child);
        console.log(
          'doesHaveCycle result: ',
          doesHaveCycle,
          'when exploring nodeVal',
          nodeVal,
          'and child',
          child
        );
        return doesHaveCycle;
      });
      if (cycle) return true;
      // now this obviously works
      console.log('if above was true... shouldnt come here');
    

    一旦找到循环,您可能还想跳出 while 循环:

    let doesItHaveCycle = false;
    while (whtSet.size > 0) {
      doesItHaveCycle = hasCycle(unvisitedValues.next().value); // Expore any unexplored nodeVal
      if (doesItHaveCycle) break;
    }
    

    因为,我们并没有进行完整的 DFS,而是在看到循环后立即返回。这使得当前节点为灰色,并且它的一些子节点未被访问。如果我们想在检测到循环后继续,我们需要访问所有子节点,将当前节点更改为黑色,如果有循环,则然后返回。

    【讨论】:

      猜你喜欢
      • 2019-08-12
      • 1970-01-01
      • 2016-09-10
      • 2021-04-24
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 1970-01-01
      • 2021-03-11
      相关资源
      最近更新 更多