【问题标题】:Traversing graph using while loop iteration使用while循环迭代遍历图
【发布时间】:2016-10-01 11:16:09
【问题描述】:

我正在使用cytoscape.js 创建图表。

我想穿过节点(有方向),但只到那些与包含特定值的边相连的节点。

我已经用这样的递归做到了

function traverse(node, requiredValue, visitedNodes) {
  // break if we visit a node twice
  if (visitedNodes.map(visitedNode => visitedNode.id()).includes(node.id())) {
    return visitedNodes;
  }

  // add visited node to collection
  visitedNodes.push(node);

  // select node
  node.select();

  // only travel through valid edges
  const edgesTo = node.outgoers(function () {
    return this.isEdge() && this.data('requiredValues').includes(requiredValue);
  });

  // break if no valid connected edges
  if (edgesTo.empty()) {
    return visitedNodes;
  }

  // travel through edges
  edgesTo.forEach(edge => {
    edge.select()
    return traverse(edge.target(), agent, visitedNodes);
  });
}

它似乎有效,但我不太擅长算法,所以我不确定这是否是构建它的聪明方法。我读过一些关于广度优先和深度优先搜索算法的文章,但我不确定是否是我需要的那些算法。

是否可以在不使用递归的情况下遍历树?我也尝试过使用while 循环,但由于它是一棵树,我认为我不能只使用

node = rootNode;

while (true) {
  // find leaving edges
  ...

  edgesTo.forEach(edge => {
    // set new node to traverse
    node = edge.target;
  }
}

因为我猜edgesTo.forEach() 将在进入while 循环中的下一个迭代之前完成。我需要它“同时”遍历不同的分支。

我可以从http://js.cytoscape.org/#collection/algorithms 看到该库有多种算法(包括 bfs 和 dfs),但是当我想遍历树而不是搜索特定节点时是否需要这些算法?

【问题讨论】:

    标签: javascript algorithm tree graph-algorithm cytoscape.js


    【解决方案1】:

    BFS 和 DFS 是通用的图遍历算法。它们不仅用于搜索某些特定节点。很多算法都有 BFS 和 DFS 作为子程序。

    您的代码基本上在图表上执行 DFS。您将忽略所有不需要的边并以深度优先的方式遍历图形的其余部分。

    是的,可以同时使用 DFS 和 BFS 遍历图形而无需递归,并且只使用一些特定的边。你只需要忽略不需要的边缘。

    BFS:

    Breadth-First-Search(Graph, root):
         create empty queue Q       
         visited.put(root)
         Q.enqueue(root)                      
         while Q is not empty:             
            current = Q.dequeue()     
            for each edge that edge.startpoint == current:
                 if current has required value AND edge.endpoint is not in visited:
                     Q.enqueue(edge.endpoint)
                     visited.put(edge.endpoint)
    

    DFS:

    procedure DFS-iterative(G,v):
          let S be a stack
          S.push(v)
          while S is not empty:
              v = S.pop()
              if v is not labeled as discovered:
                  label v as discovered
                  for all edges from v to w in G.adjacentEdges(v) :
                      if edge has required value:
                           S.push(w)
    

    【讨论】:

    • 谢谢!我会尝试用你的代码来实现它。但是在您的 DFS 代码 sn-p 中,您在倒数第二行有 if ... and w is not discovered,在倒数第五行有 if v is not labeled as discovered。检查两次不是多余的吗?
    【解决方案2】:

    我将把我的答案分解为您明确或隐含地提出的几个问题:

    一般的 BFS 和 DFS 算法

    BFS 和 DFS 是遍历连接图(或图的连接组件)的算法。它们允许您从特定的起始节点访问 所有 连接的节点(它们不会像您暗示的那样搜索特定节点,尽管它们可以以这种方式使用),并且顺序不同他们遍历图(Breadth-Frist,意味着在进入下一层邻居之前访问一个节点的所有直接邻居,与 Depth-First 相比,这意味着首先追求一个从一个直接邻居开始并更深入的分支,在继续下一个分支之前访问所有通过该特定邻居节点连接到起始节点的节点,这些节点是通过下一个直接邻居连接的所有节点。

    与您的要求相关的 BFS 和 DFS

    和以前一样,这两种算法都会访问图的连接组件中的所有节点(通过遍历边可以从起始节点到达的所有节点)。由于您有不同的要求(仅使用特定值的边进行遍历),我建议您自己更改任一算法,并添加此需求。您实际上已经这样做了:您的算法在某种意义上是 DFS 的后代/版本。

    BFS 和 DFS 以及递归

    BFSdoes not normally include recursion并使用数据结构(队列)来实现其遍历顺序。

    DFS 很容易通过递归实现(您实现算法的直观方式与此非常相似)。但是您可以在没有递归的情况下实现 DFS - 使用数据结构(堆栈)来实现其遍历顺序(本质上,递归隐式使用调用堆栈作为数据结构,因此它没有那么不同,尽管递归有更多的开销来处理与函数调用及其环境)。 您可以在wiki of DFS 中看到一个伪代码。这里是为了方便:

    procedure DFS-iterative(G,v):
      let S be a stack
      S.push(v)
      while S is not empty
          v = S.pop()
          if v is not labeled as discovered:
              label v as discovered
              for all edges from v to w in G.adjacentEdges(v) do
                  S.push(w)
    

    【讨论】:

    • 感谢您的精彩回答。问题是如果我查看js.cytoscape.org/#eles.depthFirstSearch,我只得到参数e,它表示将前一个节点连接到当前节点的边,所以使用这个预先实现的算法,我无法控制它是否应该访问过这个节点,因为它已经完成了。在options 参数中的visit 参数函数内部,我可以执行if (e && !e.data('reqValues').includes(reqValue)) {.. 之类的操作,但它会在访问其余节点之前结束visit 函数,因此它也不是一个选项。
    • 你说得对,我认为你不应该使用这个现成的功能,而应该自己实现它。这就是我在“与您的要求相关的 BFS 和 DFS”中所写的内容。在“BFS 和 DFS 和递归”中,我为您提供了 DFS 循环而不是递归伪代码,因此您可以将您的要求添加到 for(基本上将其更改为 for all edges from v to w in G.adjacentEdges(v) that are of the value requiredValue do)。但是 Tempux 添加的答案包括两种算法的实现,包括根据您的要求进行的调整。
    • visit() 不用于指定可以遍历哪些元素。它用于指定目标,或用于迭代/遍历回调。只需 filter 指定要在其上运行 BFS/DFS 的图形子集的元素。很多关于图论的文献都隐含地将整个图用于算法,因此很容易忘记您可以使用子图。
    【解决方案3】:

    Cytoscape 包含许多开箱即用的common graph theory algorithms。这些算法甚至可以让您指定要包含/调用的元素。

    您可以使用像.outgoers() 这样的低级遍历 API 自己重新实现 BFS 遍历算法,但为什么要重新发明轮子呢?

    BFSDFS 内置于 Cytoscape:

    cy.elements().stdFilter(function( ele ){
      return ele.isNode() ? includeThisNode( ele ) : includeThisEdge( ele ); // eles to include
    }).bfs({
      visit: function(){ ... } // called when new ele visited
    });
    

    指定 includeThisNode()includeThisEdge() 以满足您应该允许遍历哪些元素的标准。

    如果您有具有特定条件的目标节点,则在满足这些条件时在 visit() 中返回 true。

    【讨论】:

    • 很棒的图书馆! :-D 我不知道它是如何实现的,但看起来它会首先获取所有有效元素,但是即使遍历不会访问节点,某些节点也会被视为有效,因为没有有效的边会导致它。那么,与只考虑合法可以到达的节点的算法相比,cy.elements.filter() 返回一个不必要的大集合不是无效吗?我也许可以使用ele.isNode() ? ele.connectedEdges().filterFn(edge => edge.target() === ele && edge.data('agents').includes(agent) : ele.data('agents').includes(agent)
    • 唯一重要的限制是排除。您必须排除绝对不想遍历的节点和边。让 BFS 为您处理可达性。
    猜你喜欢
    • 2019-07-28
    • 1970-01-01
    • 1970-01-01
    • 2019-08-13
    • 1970-01-01
    • 2021-11-29
    • 1970-01-01
    • 2020-08-20
    • 1970-01-01
    相关资源
    最近更新 更多