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

13

我正在尝试递归地搜索这个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);
};
3个回答

17
你的代码只是缺少一个循环来检查节点的每个子节点在child数组中。这个递归函数将返回节点的label属性,如果树中不存在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));

你还可以使用显式堆栈进行迭代,这样不会导致堆栈溢出(但请注意,由于扩展语法的原因,某些JS引擎中的简写stack.push(...curr.child);可能会导致参数大小溢出,因此对于大型子数组,请使用显式循环)。

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了。

3

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
  )

相关的高阶函数实现递归搜索


0

这里有一个使用 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}
<script src="https://bundle.run/object-scan@13.7.1"></script>

声明:本人是object-scan的作者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接