Javascript:DFS遍历涉及I / O的有向树

时间:2013-08-18 16:43:07

标签: javascript node.js tree

给定一个每个节点具有可变数量的子节点的有向树T,我想找到一个从根开始的“好”节点的PATH_SIZE大小的路径。

每个节点都有isGood()方法和getChildren()方法,可以按预期工作。

一个简单的DFS递归解决方案看起来像这样:(如果我错了请纠正我)

function findGoodPath(node, depth){
    if(!node.isGood()){
        return null;
    } else if (depth==PATH_SIZE){
        return [node];
    }
    var children = node.getChildren();
    for (var i=0; i<children.length; i++){
        var result = findGoodPath(children[i], depth+1);
        if (result){
            return result.concat([node]);
        }
    }
    return null;
}

调用findGoodPath(root, 1)应该找到一个结果(如果存在)。

现在对于问题:节点对象的getChildren()方法实际上是在后台进行I / O的异步方法。它什么都不返回,并期望一个回调参数来处理返回的子节点。

修改后的代码解决方案(错误)可能如下所示:

function findGoodPath(node, depth){
    if(!node.isGood()){
        return null;
    } else if (depth==PATH_SIZE){
        return [node];
    }
    node.getChildren(function(children){
        for (var i=0; i<children.length; i++){
            var result = findGoodPath(children[i], depth+1);
            if (result){
                return result.concat([node]);
            }
        }
    });
}

此解决方案不起作用:单个节点的子节点的所有getChildren方法将立即被调用,因此它实际上将执行BFS。更糟糕的是,return语句与匿名回调函数相关联,并将在封闭函数运行完毕后执行。

很明显,需要某种流量控制机制。这个问题的简单而优雅的解决方案是什么?

更新:我接受了Sebastien的答案,因为它通过递归解决了这个问题,这就是我提出问题的方式。我还发布了一个使用async'swhilst循环的答案,这就是我最终使用的内容。 Sebastien非常友好地对这两种方法here进行了基准测试。 (剧透:性能相同)

3 个答案:

答案 0 :(得分:1)

首先,如果您希望深度等于PATH_SIZE,我认为您必须调用findGoodPath(children [i],depth + 1)。

然后,你确实有关闭的问题。通过异步调用,您始终可以使用不是您想要的节点实例进行连接。

你可以采取的一种方法是:

node.getChildren((function(_node) {
  return function(children){
    for (var i=0; i<children.length; i++){
      var result = findGoodPath(children[i], depth);
        if (result){
          return result.concat([_node]);
        }
      }
    });
})(node));

但我认为这只是问题的一部分,因为你将同步功能与异步功能混合在一起。 这一行:

var result = findGoodPath(children[i], depth)

是作为同步调用编写的,而findGoodPath是一个异步函数,所以它也必须用回调函数编写!

希望有所帮助

ps:有一个jsfiddle会有所帮助......

更新:试一试。由于我无法测试,它不起作用,但这是主意。我无法弄清楚你是否需要在第二个findGoodPath调用中创建另一个范围,就像在getChildren调用中一样

function findGoodPath(node, depth, callback){
  if(!node.isGood()){
    return callback(null);
  } else if (depth==PATH_SIZE){
    return callback([node]);
  }
  node.getChildren((function(_node, _callback) {

    return function(children){
      var node = _node, callback = _callback;

      for (var i=0; i<children.length; i++){
        findGoodPath(children[i], depth, function(result) {
          if (result){
            return callback(result.concat([node]));
          }
        });
      }
    });
  })(node, callback));
}

答案 1 :(得分:0)

我现在不是100%专注,但我几乎肯定Async.js seriestasks对你来说是正确的解决方案(如果不是seriestasks我愿意打赌,Async.js中还有另一个控制流程诀窍。

答案 2 :(得分:0)

好的,有几种方法可以实现异步DFS遍历。由于异步递归有一种变得有些丑陋的倾向,我决定摆脱递归。

我首先使用while循环而不是递归重新实现了函数的同步版本:

function findGoodPathLoop(root){
    var nodesToVisit = [{data: root, path:[]}];
    while (nodesToVisit.length>0){
        var currentNode = nodesToVisit.pop();
        if (currentNode.data.isGood()){
            var newPath = currentNode.path.concat(currentNode.data);
            if (newPath.length==PATH_SIZE){
                return newPath;
            } else {
                var childrenNodes = currentNode.data.getChildren().map(function(child){
                    return {data: child, path: newPath};
                });
                nodesToVisit = nodesToVisit.concat(childrenNodes);
            }
        }
    }
    return null;
}

注意我保存了每个节点的整个路径,这不是必需的,你可以保存深度并维护当前路径的数组,虽然它有点麻烦。

然后我使用async库将此函数转换为异步函数,将标准while()函数替换为async的whilst()

function findGoodPathAsync(root, pathCallback){
    var result = null;
    var nodesToVisit = [{data: root, path:[]}];
    async.whilst(
        function(){
            return nodesToVisit.length>0 && result==null ;
        },
        function(next) {
            var currentNode = nodesToVisit.pop();
            if (currentNode.data.isGood()){
                var newPath = currentNode.path.concat(currentNode);
                if(newPath.length==PATH_SIZE){
                    result = newPath;
                    next();
                } else {
                    currentNode.data.getChildren(function(children){
                        var childrenNodes = children.map(function(child){
                            return {data: child, path: newPath};
                        });
                        nodesToVisit = nodesToVisit.concat(childrenNodes);
                        next();
                    });
                }
            } else {
                next();
            }
        },
        function(err) {
            //error first style callback
            pathCallback(err, result);
        }
    );
}

不是一个漂亮的,但它是可读的,它完成了这项工作。