JavaScript中嵌套对象结构中的递归树搜索

Posted

技术标签:

【中文标题】JavaScript中嵌套对象结构中的递归树搜索【英文标题】:Recursive tree search in a nested object structure in JavaScript 【发布时间】:2019-02-03 14:11:03 【问题描述】:

我试图弄清楚如何递归地在这个 JSON 对象中搜索一个节点。我尝试了一些东西但无法得到它:

var tree = 
    "id": 1,
    "label": "A",
    "child": [
        
            "id": 2,
            "label": "B",
            "child": [
                
                    "id": 5,
                    "label": "E",
                    "child": []
                ,
                
                    "id": 6,
                    "label": "F",
                    "child": []
                ,
                
                    "id": 7,
                    "label": "G",
                    "child": []
                
            ]
        ,
        
            "id": 3,
            "label": "C",
            "child": []
        ,
        
            "id": 4,
            "label": "D",
            "child": [
                
                    "id": 8,
                    "label": "H",
                    "child": []
                ,
                
                    "id": 9,
                    "label": "I",
                    "child": []
                
            ]
        
    ]
;

这是我的非工作解决方案,这可能是因为第一个节点只是一个值,而孩子们在数组中:

function scan(id, tree) 
    if(tree.id == id) 
        return tree.label;
    

    if(tree.child == 0) 
        return
    

    return scan(tree.child);
;

【问题讨论】:

【参考方案1】:

您的代码只是缺少一个循环来检查child 数组中节点的每个子节点。此递归函数将返回节点的label 属性,如果树中不存在标签,则返回undefined

const search = (tree, target) => 
  if (tree.id === target) 
    return tree.label;
  
  
  for (const child of tree.child) 
    const found = search(child, target);
    
    if (found) 
      return found;
    
  
;

const tree = "id":1,"label":"A","child":["id":2,"label":"B","child":["id":5,"label":"E","child":[],"id":6,"label":"F","child":[],"id":7,"label":"G","child":[]],"id":3,"label":"C","child":[],"id":4,"label":"D","child":["id":8,"label":"H","child":[],"id":9,"label":"I","child":[]]];

console.log(search(tree, 1));
console.log(search(tree, 6));
console.log(search(tree, 99));

您也可以使用不会导致堆栈溢出的显式堆栈进行迭代(但请注意,由于扩展语法,速记 stack.push(...curr.child); 可能会溢出某些 JS 引擎的参数大小,因此请使用显式循环或 concat 用于大型子数组):

const search = (tree, target) => 
  for (const stack = [tree]; stack.length;) 
    const curr = stack.pop();
    
    if (curr.id === target) 
      return curr.label;
    

    stack.push(...curr.child);
  
;

const tree = "id":1,"label":"A","child":["id":2,"label":"B","child":["id":5,"label":"E","child":[],"id":6,"label":"F","child":[],"id":7,"label":"G","child":[]],"id":3,"label":"C","child":[],"id":4,"label":"D","child":["id":8,"label":"H","child":[],"id":9,"label":"I","child":[]]];

for (let i = 0; ++i < 12; console.log(search(tree, i)));

更通用的设计会返回节点本身,并让调用者访问.label 属性,如果他们愿意,或者以其他方式使用对象。

请注意,JSON 纯粹是一种用于序列化(字符串化、原始)数据的字符串格式。一旦您将 JSON 反序列化为 javascript 对象结构(如此处所示),它就不再是 JSON。

【讨论】:

【参考方案2】:

scan 可以使用第三个参数递归编写,该参数模拟要扫描的节点队列

const scan = (id, tree = , queue = [ tree ]) =>
  // if id matches node id, return node label
  id === tree.id
    ? tree.label

  // base case: queue is empty
  // id was not found, return false
  : queue.length === 0
    ? false

  // inductive case: at least one node
  // recur on next tree node, append node children to queue
  : scan (id, queue[0], queue.slice(1).concat(queue[0].child))

因为 JavaScript 支持默认参数,所以 scan 的调用站点不会改变

console.log
  ( scan (1, tree)  // "A"
  , scan (3, tree)  // "C"
  , scan (9, tree)  // "I"
  , scan (99, tree) // false
  )

在下面的浏览器中验证它是否有效

const scan = (id, tree = , queue = [ tree ]) =>
  id === tree.id
    ? tree.label
  : queue.length === 0
    ? false
  : scan (id, queue[0], queue.slice(1).concat(queue[0].child))

const tree =
   id: 1
  , label: "A"
  , child:
      [  id: 2
        , label: "B"
        , child:
            [  id: 5
              , label: "E"
              , child: []
              
            ,  id: 6
              , label: "F"
              , child: []
              
            ,  id: 7
              , label: "G"
              , child: []
              
            ]
        
      ,  id: 3
        , label: "C"
        , child: []
        
      ,  id: 4
        , label: "D"
        , child:
            [  id: 8
              , label: "H"
              , child: []
              
            ,  id: 9
              , label: "I"
              , child: []
              
            ]
        
      ]
  

console.log
  ( scan (1, tree)  // "A"
  , scan (3, tree)  // "C"
  , scan (9, tree)  // "I"
  , scan (99, tree) // false
  )

相关recursive search using higher-order functions

【讨论】:

【参考方案3】:

这是使用object-scan的解决方案

// const objectScan = require('object-scan');

const tree = "id":1,"label":"A","child":["id":2,"label":"B","child":["id":5,"label":"E","child":[],"id":6,"label":"F","child":[],"id":7,"label":"G","child":[]],"id":3,"label":"C","child":[],"id":4,"label":"D","child":["id":8,"label":"H","child":[],"id":9,"label":"I","child":[]]];

const search = (obj, id) => objectScan(['**.id'], 
  abort: true,
  filterFn: ( value, parent, context ) => 
    if (value === id) 
      context.push(parent.label);
      return true;
    
    return false;
  
)(obj, [])[0];

console.log(search(tree, 1));
// => A
console.log(search(tree, 6));
// => F
console.log(search(tree, 99));
// => undefined
.as-console-wrapper max-height: 100% !important; top: 0
&lt;script src="https://bundle.run/object-scan@13.7.1"&gt;&lt;/script&gt;

免责声明:我是object-scan的作者

【讨论】:

以上是关于JavaScript中嵌套对象结构中的递归树搜索的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用递归从嵌套的 javascript 对象树中提取项目

JSON对象中的JavaScript递归搜索

Javascript递归导致循环结构

树结构中的递归回溯

在Javascript中将数组转换为嵌套的JSON?

递归 JavaScript 函数正在丢失返回值