Javascript:涉及I/O的定向树的DFS遍历
给定一个每个节点具有可变子节点数的有向树T,我想找到一条路径,路径的大小为“good”节点的大小,从根开始 每个节点都有一个Javascript:涉及I/O的定向树的DFS遍历,javascript,node.js,tree,Javascript,Node.js,Tree,给定一个每个节点具有可变子节点数的有向树T,我想找到一条路径,路径的大小为“good”节点的大小,从根开始 每个节点都有一个isGood()方法和一个getChildren()方法,可以按预期工作 一个简单的DFS递归解决方案如下:(如果我错了,请纠正我) 这个解决方案行不通:一个节点的子节点的所有getChildren方法都将被一次调用,因此它实际上将执行BFS。更糟糕的是,return语句与匿名回调函数关联,并将在封闭函数完成运行后执行 很明显,需要某种流量控制机制。这个问题的简单而优雅的解
isGood()
方法和一个getChildren()
方法,可以按预期工作
一个简单的DFS递归解决方案如下:(如果我错了,请纠正我)
这个解决方案行不通:一个节点的子节点的所有getChildren方法都将被一次调用,因此它实际上将执行BFS。更糟糕的是,return语句与匿名回调函数关联,并将在封闭函数完成运行后执行
很明显,需要某种流量控制机制。这个问题的简单而优雅的解决方案是什么
更新:我接受了塞巴斯蒂安的答案,因为它通过递归解决了这个问题,这就是我提出问题的方式。我还发布了一个使用库循环的答案,这就是我最终使用的。Sebastien很好地对这两种方法进行了基准测试。(扰流板:性能是相同的)我现在不是100%关注,但我几乎可以肯定这是适合您的正确解决方案(如果不是seriestasks,我愿意打赌Async.js中有另一个控制流可以实现这一点。首先,我认为您必须调用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));
是作为同步调用编写的,而findGoodPath是一个异步函数,因此它也必须使用回调编写
希望能有帮助
ps:有一个JSFIDLE会有帮助
更新:只是一个尝试。由于我无法测试,它不起作用,但这是我的想法。我无法确定是否需要在第二个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));
}
函数findGoodPath(节点、深度、回调){
如果(!node.isGood()){
返回回调(null);
}else if(深度==路径大小){
返回回调([node]);
}
getChildren((函数(\u节点,\u回调){
返回函数(子级){
var node=\u node,callback=\u callback;
对于(var i=0;iOK),有几种方法可以实现异步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;
}
注意:我为每个节点保存了整个路径,这不是必须的,您可以只保存深度并维护当前路径的数组,尽管它有点混乱
然后,我使用库将此函数转换为异步函数,将标准的while()
函数替换为异步的while()
:
这不是一个漂亮的例子,但它可读性好,而且可以完成任务。我的错,当然函数需要一个回调参数。非函数代码示例只是为了展示将其转换为异步的一些问题。我将在上午查看它,但我当前的方向是转向非递归解决方案,然后使用它使用js异步库中的Actions来控制流。将异步函数和递归结合起来是一个很好的挑战,但我担心它会创建不可维护的代码(至少对我来说是这样)。没错!但是能够以异步的方式完成这项工作将非常有效,而且肯定是一个很好的挑战:)我无法让它工作。你能给我一把小提琴吗?你可以用我的小提琴作为基础:给你:(我刚刚签入了jsperf,对于这种类型的节点,两种方法的速度都相同:)
var result = findGoodPath(children[i], depth)
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));
}
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;
}
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);
}
);
}