如何使用 JavaScript 在树中查找节点

IT技术 javascript
2021-01-28 15:49:36

我有一个对象文字,它本质上是一棵树,没有固定的级别数。如何在树中搜索特定节点,然后在 javascript 中以有效方式找到该节点时返回该节点?

基本上我有一个这样的树,想找到标题为“randomNode_1”的节点

var data = [
{
title: 'topNode',
 children: [
   {
       title: 'node1',
       children: [
       {
           title: 'randomNode_1'
       },
       {   
           title: 'node2',
           children: [
           {
               title: 'randomNode_2',
               children:[
               {   
                   title: 'node2',
                   children: [
                   {
                       title: 'randomNode_3',
                   }]
               }
               ]
           }]
       }]
   }
  ]
 }];
6个回答

这个答案基于@Ravindra 的答案,但具有真正的递归。

function searchTree(element, matchingTitle){
     if(element.title == matchingTitle){
          return element;
     }else if (element.children != null){
          var i;
          var result = null;
          for(i=0; result == null && i < element.children.length; i++){
               result = searchTree(element.children[i], matchingTitle);
          }
          return result;
     }
     return null;
}

然后你可以调用它:

var element = data[0];
var result = searchTree(element, 'randomNode_1');
当然,如果结果返回为非空值,则父节点必须在路径上,因此将当前节点推送到结果路径数组上,该数组可以作为参数添加到函数中或暴露在外部作用域中。
2021-03-15 15:49:36
有一种方法可以修改此代码,以便将所有标题键推送到数组中,例如 titles = ["topNode", "node1", "randomNode_1", "node2", "randomNode2", "node2", "randomNode3 "]
2021-04-03 15:49:36
我可以注意迭代路径吗?
2021-04-09 15:49:36

这是一个迭代解决方案:

var stack = [], node, ii;
stack.push(root);

while (stack.length > 0) {
    node = stack.pop();
    if (node.title == 'randomNode_1') {
        // Found it!
        return node;
    } else if (node.children && node.children.length) {
        for (ii = 0; ii < node.children.length; ii += 1) {
            stack.push(node.children[ii]);
        }
    }
}

// Didn't find it. Return null.
return null;
事实上,这段代码没有返回任何内容。
2021-03-19 15:49:36
如果未找到匹配项,这将返回最后一个节点
2021-03-28 15:49:36
我想当我在很多个月前写这么多的时候,我的思考过程是你会在这个方法中对节点做一些事情,而不是返回它。但谁能说得清呢?过去的我反复无常,容易出错。完全不像 Current Me ;) 但我现在改变了代码以返回一些东西。无论哪种方式,迭代解决方案的基本框架都在那里。
2021-03-31 15:49:36
漂亮干净!可以使用新的 es6 语法缩短一点。
2021-04-12 15:49:36
这应该是一个可以接受的答案,其他递归答案容易出现堆栈溢出,尤其是在 javascript 中
2021-04-14 15:49:36

这是一个使用 Stack 方法的迭代函数,其灵感来自FishBasketGordo 的答案,但利用一些ES2015语法来缩短事情。

由于这个问题已经被查看了很多次,我决定更新我的答案,以提供一个带有参数的函数,使其更加灵活:

function search (tree, value, key = 'id', reverse = false) {
  const stack = [ tree[0] ]
  while (stack.length) {
    const node = stack[reverse ? 'pop' : 'shift']()
    if (node[key] === value) return node
    node.children && stack.push(...node.children)
  }
  return null
}

这样,现在可以传递数据tree本身、需要value搜索的数据以及key可以具有所需值的属性

search(data, 'randomNode_2', 'title')

最后,我使用的原始答案Array.pop导致在多个匹配的情况下匹配最后一个项目。事实上,有些事情可能真的很令人困惑。受到Superole 评论的启发,我Array.shift现在已经使用了它,所以先进先出行为是默认的。

如果你真的想要旧的后进先出行为,我提供了一个额外的 arg reverse

search(data, 'randomNode_2', 'title', true)
这么干净的答案。但是,当节点嵌套时,它会因无法读取属性 'Symbol(Symbol.iterator)' of stack.push 错误而失败
2021-03-20 15:49:36
我可以注意树迭代路径吗?
2021-03-21 15:49:36
谢谢@iamwhitebox,你说得对。node.children在尝试使用...之前,我们需要检查是否有或者甚至是&&我在更新的答案中选择的简单短路条件。
2021-03-21 15:49:36
除了树搜索之外,TIL 条件方法也是一回事。脑洞大开。
2021-03-24 15:49:36
将 的结果stack.pop() || null放在一个变量中,这样你就console.log可以在返回之前使用它。
2021-03-29 15:49:36

我的回答灵感来自 FishBasketGordo 的迭代回答。它有点复杂,但也更灵活,您可以拥有多个根节点。

/**searchs through all arrays of the tree if the for a value from a property
 * @param aTree : the tree array
 * @param fCompair : This function will receive each node. It's upon you to define which 
                     condition is necessary for the match. It must return true if the condition is matched. Example:
                        function(oNode){ if(oNode["Name"] === "AA") return true; }
 * @param bGreedy? : us true to do not stop after the first match, default is false
 * @return an array with references to the nodes for which fCompair was true; In case no node was found an empty array
 *         will be returned
*/
var _searchTree = function(aTree, fCompair, bGreedy){
    var aInnerTree = []; // will contain the inner children
    var oNode; // always the current node
    var aReturnNodes = []; // the nodes array which will returned

    // 1. loop through all root nodes so we don't touch the tree structure
    for(keysTree in aTree) {
        aInnerTree.push(aTree[keysTree]);
    }
    while(aInnerTree.length > 0) {
        oNode = aInnerTree.pop();
        // check current node
        if( fCompair(oNode) ){
            aReturnNodes.push(oNode);
            if(!bGreedy){
                return aReturnNodes;
            }
        } else { // if (node.children && node.children.length) {
            // find other objects, 1. check all properties of the node if they are arrays
            for(keysNode in oNode){
                // true if the property is an array
                if(oNode[keysNode] instanceof Array){
                    // 2. push all array object to aInnerTree to search in those later
                    for (var i = 0; i < oNode[keysNode].length; i++) {
                        aInnerTree.push(oNode[keysNode][i]);
                    }
                }
            }
        }
    }
    return aReturnNodes; // someone was greedy
}

最后你可以像这样使用这个函数:

var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, false);
console.log("Node with title found: ");
console.log(foundNodes[0]);

如果你想找到所有具有这个标题的节点,你可以简单地切换 bGreedy 参数:

var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, true);
console.log("NodeS with title found: ");
console.log(foundNodes);

你必须使用递归。

var currChild = data[0];
function searchTree(currChild, searchString){
     if(currChild.title == searchString){
          return currChild;
     }else if (currChild.children != null){
          for(i=0; i < currChild.children.length; i ++){
               if (currChild.children[i].title ==searchString){
                    return currChild.children[i];
               }else{
                    searchTree(currChild.children[i], searchString);
               }
          }
          return null;
     }
     return null;
}