如何将含ID与子节点引用的对象数组转换为嵌套树形结构?
如何将扁平nodes数组转换为嵌套树形结构?
问题背景
你有一组扁平的节点数据,需要将其转换为嵌套的树形结构,同时希望适配任意数量的节点和任意深度的层级。
原始输入数据
const nodes = [ { name: "Ben", id: 1, next: [2, 3], depth: 0 }, { name: "Mike", id: 2, next: [4, 5], depth: 1 }, { name: "Jen", id: 3, next: [6], depth: 1 }, { name: "Sue", id: 4, next: [], depth: 2 }, { name: "Jeff", id: 5, next: [], depth: 2 }, { name: "Bob", id: 6, next: [], depth: 3 } ];
目标树形结构
const root = { name: "Ben", children: [ { name: "Mike", children: [ { name: "Sue" }, { name: "Jeff" } ] }, { name: "Jen", children: [ { name: "Bob" } ] } ] };
已实现的部分逻辑
你已经完成了根节点第一层子节点的添加,但不知道如何处理更深层级的子节点:
const root = { name: nodes[0].name }; root.children = []; nodes[0].next.map(function (next) { nodes.map((node, i) => { if (next === node.id) { root.children.push({name: nodes[i].name}) } }) });
你的疑问
如何动态创建children数组并添加正确的属性,以适配任意数量的节点和任意深度的树形结构?
解决方案
要处理任意深度的树形结构,我们可以通过建立节点映射表+批量关联子节点的方式来实现,效率更高且能适配所有情况。
步骤1:建立ID到节点的映射表
首先把所有节点存入一个以id为键的对象中,这样可以快速通过id找到对应的节点,避免重复遍历数组:
const nodeMap = {}; nodes.forEach(node => { // 先创建每个节点的基础结构(只保留name) nodeMap[node.id] = { name: node.name }; });
步骤2:批量关联子节点
遍历每个原始节点,根据它的next数组,把对应的子节点添加到映射表中节点的children属性里:
nodes.forEach(node => { // 如果当前节点有子节点ID,就生成对应的children数组 if (node.next.length > 0) { nodeMap[node.id].children = node.next.map(childId => nodeMap[childId]); } });
步骤3:获取根节点
最后从映射表中取出根节点(这里我们用nodes[0].id,你也可以根据depth: 0来筛选):
const root = nodeMap[nodes[0].id];
完整代码示例
const nodes = [ { name: "Ben", id: 1, next: [2, 3], depth: 0 }, { name: "Mike", id: 2, next: [4, 5], depth: 1 }, { name: "Jen", id: 3, next: [6], depth: 1 }, { name: "Sue", id: 4, next: [], depth: 2 }, { name: "Jeff", id: 5, next: [], depth: 2 }, { name: "Bob", id: 6, next: [], depth: 3 } ]; // 1. 建立节点映射表 const nodeMap = {}; nodes.forEach(node => { nodeMap[node.id] = { name: node.name }; }); // 2. 关联子节点 nodes.forEach(node => { if (node.next.length) { nodeMap[node.id].children = node.next.map(id => nodeMap[id]); } }); // 3. 获取根节点 const root = nodeMap[nodes[0].id]; console.log(root);
方案优势
- 高效性:时间复杂度为O(n),相比嵌套遍历的O(n²),在节点数量多时性能提升明显。
- 通用性:不管树形结构有多深,只要
next数组正确指向子节点ID,就能自动构建对应的层级。 - 灵活性:适配任意数量的节点,无需修改代码结构。
备选:递归写法
如果你更喜欢直观的递归逻辑,也可以用递归函数逐个构建子树:
function buildTree(nodeId) { // 找到当前ID对应的原始节点 const currentNode = nodes.find(n => n.id === nodeId); // 创建树形节点 const treeNode = { name: currentNode.name }; // 如果有子节点,递归构建 if (currentNode.next.length > 0) { treeNode.children = currentNode.next.map(childId => buildTree(childId)); } return treeNode; } // 从根节点ID开始构建 const root = buildTree(nodes[0].id);
注意:这种写法中find方法会遍历数组,当节点数量较多时,性能不如映射表方案。
内容的提问来源于stack exchange,提问作者Vialito
相关产品推荐
相关产品推荐

