给定一个每个节点具有可变数量的子节点的有向树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's库whilst循环的答案,这就是我最终使用的内容。 Sebastien非常友好地对这两种方法here进行了基准测试。 (剧透:性能相同)
答案 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);
}
);
}
不是一个漂亮的,但它是可读的,它完成了这项工作。