You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript中如何高效获取树结构指定节点的所有后代节点

高效获取指定节点的所有后代节点

问题背景

给定以下扁平结构的树节点数组:

[
  { id: "1" },
  { id: "2" },
  { id: "3" },
  { id: "2.1", parent_id: "2" },
  { id: "3.1", parent_id: "3" },
  { id: "3.2", parent_id: "3" },
  { id: "3.1.1", parent_id: "3.1" },
  { id: "3.1.1.1", parent_id: "3.1.1" },
  { id: "3.1.1.1.1", parent_id: "3.1.1.1" },
  { id: "bohemian rhapsody", parent_id: "3.2" }
]

需求是从任意指定id的节点出发,获取其所有后代节点(包括自身)。原实现通过递归遍历整个数组完成,但每次递归都要扫描全量数据,效率较低,需要性能最优的方案(不关心遍历顺序是广度优先还是深度优先)。

原实现代码:

let source = []; // 数据源
let getDescendants = (id, source) => {
    let result = [];
  
    for (let node of source) {
     if (node.id === id) {
        result.push(node);
     } else if (node.parent_id === id) {
        result.push(...getDescendants(node.id, source));
     }
    }
  
    return result;
};

原方案性能问题

原方案的时间复杂度为O(n²):每次递归都要遍历整个数组,对于链式结构(如3.1 → 3.1.1 → 3.1.1.1 → ...),每个节点都需要扫描全量数组一次,数据量越大性能越差。

最优性能方案

核心思路是预处理构建映射表,将父节点与子节点的关系提前存储,后续查询时直接通过映射快速获取子节点,无需再遍历全量数组。

步骤1:构建映射表

一次性遍历数组,生成两个映射:

  • nodeMap:键为节点id,值为节点本身,用于快速查找目标节点
  • childrenMap:键为父节点id,值为子节点数组,用于快速获取某节点的所有子节点
const buildTreeMaps = (source) => {
  const nodeMap = new Map();
  const childrenMap = new Map();

  for (const node of source) {
    nodeMap.set(node.id, node);
    // 初始化父节点的子数组容器
    if (!childrenMap.has(node.parent_id)) {
      childrenMap.set(node.parent_id, []);
    }
    // 若节点有父id,将其加入对应父节点的子数组
    if (node.parent_id) {
      childrenMap.get(node.parent_id).push(node);
    }
  }

  return { nodeMap, childrenMap };
};

步骤2:高效获取后代节点

基于映射表,用迭代方式(避免深层递归栈溢出)遍历节点及其所有子节点:

const getDescendants = (targetId, { nodeMap, childrenMap }) => {
  const result = [];
  const queue = [];

  // 先加入目标节点本身
  const targetNode = nodeMap.get(targetId);
  if (targetNode) {
    queue.push(targetNode);
  }

  while (queue.length > 0) {
    const currentNode = queue.shift(); // 广度优先;换成pop()则为深度优先
    result.push(currentNode);
    // 获取当前节点的所有子节点,加入队列继续遍历
    const children = childrenMap.get(currentNode.id) || [];
    queue.push(...children);
  }

  return result;
};

使用示例

// 原始数据源
const source = [
  { id: "1" },
  { id: "2" },
  { id: "3" },
  { id: "2.1", parent_id: "2" },
  { id: "3.1", parent_id: "3" },
  { id: "3.2", parent_id: "3" },
  { id: "3.1.1", parent_id: "3.1" },
  { id: "3.1.1.1", parent_id: "3.1.1" },
  { id: "3.1.1.1.1", parent_id: "3.1.1.1" },
  { id: "bohemian rhapsody", parent_id: "3.2" }
];

// 构建映射表(只需执行一次)
const treeMaps = buildTreeMaps(source);

// 获取id为"3"的所有后代节点
const descendants = getDescendants("3", treeMaps);
console.log(descendants);

性能说明

  • 预处理阶段:时间复杂度O(n),仅需执行一次
  • 查询阶段:时间复杂度O(k),其中k是目标节点及其后代的总数量,远优于原方案的O(n²)
  • 若需多次查询,该方案的性能优势会更加明显

验证结果

传入id="3"时,输出结果与预期一致(以下为广度优先顺序,改为深度优先只需将shift()换成pop()):

[
  { id: "3" },
  { id: "3.1", parent_id: "3" },
  { id: "3.2", parent_id: "3" },
  { id: "3.1.1", parent_id: "3.1" },
  { id: "bohemian rhapsody", parent_id: "3.2" },
  { id: "3.1.1.1", parent_id: "3.1.1" },
  { id: "3.1.1.1.1", parent_id: "3.1.1.1" }
]

内容的提问来源于stack exchange,提问作者zaitsman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 19:20:54