将普通数组转成树形结构

提示:以下是本篇文章正文内容,下面案例可供参考


提示:将扁平化数组转换成树形数组

1、使用递归的方法
//定义一个buildTree函数,它接受一个数组和一个可选的parentId参数
//parentId默认为null,表示从根节点开始构建树。
function buildTree(arr, parentId = null) {
  const result = [];
  const temp = arr.filter(item => item.parent_id=== parentId);
  temp.forEach(item => {
  //对于每个节点,递归调用buildTree函数来构建其子树,并将结果赋值给item.children。
    const children = buildTree(arr, item.id);
    if (children.length) {
      item.children = children;
    }
    result.push(item);
  });
  //将当前节点添加到结果数组中。最终,返回结果数组作为树的根节点。
  return result;
}
// 示例数组
const arr = [
  {id: 4, parent_id: 3},
  {id: 'aa', parent_id: 'a'},
  {id: 1, parent_id: null},
  {id: 3, parent_id: 2},
  {id: 'a', parent_id: 'a0'},
  {id: 2, parent_id: 1},
  {id: 'a0', parent_id: null}
];

const tree = buildTree(arr);
console.log(JSON.stringify(tree, null, 2));
2、使用reduce 方法
//reduce 方法用于将数组中的每个元素(i)归约为单个值(在此处是一个对象 o,用于存储树形结构)。
const tree = arr.reduce((o, i) => {
	//o[i.id] 尝试获取 o 中 i.id 对应的对象,如果不存在则创建一个空对象 {}。
	//Object.assign 将 i 的属性复制到 o[i.id] 对象中,以确保每个节点在 o 中都有唯一的标识。
    i = Object.assign(o[i.id] ??= {}, i);
    //o[i.pid ?? ''] 尝试获取 o 中 i.pid 对应的对象,如果不存在则创建一个空对象 {}。
	//o[i.pid ?? '']?.children 用来获取或初始化 o[i.pid] 的 children 数组,确保每个父节点都有一个子节点数组。
	//push(i) 将当前节点 i 添加到其父节点的 children 数组中。
    ((o[i.pid ?? ''] ??= {}).children ??= []).push(i);
    //reduce 方法返回的最终结果是一个对象 o,其中根节点的子节点数组存储在 o[''].children 中。
    return o;
}, {})['']?.children
console.log(tree);

注意事项:

  1. 根节点识别: reduce 方法中,通过 o[‘’]?.children 来获取根节点的子节点数组,前提是根节点的 id 或 pid 被设为 ‘’。
  2. 空值合并运算符(??=): 这是 ECMAScript 2022(ES2022)中的新特性,用于更简洁地处理可能为 null 或 undefined 的情况。
  3. 在JavaScript中,?? 和 ??= 是合并运算符和其赋值形式的表达式。
  • ?? 是一个逻辑运算符,用于判断左侧操作数是否为nullundefined。如果是,则返回右侧操作数,否则返回左侧操作数的值。
    示例:
let x = null ?? 'default'; // x 是 'default'
let y = 'value' ?? 'default'; // y 是 'value'
let z = 0 ?? 42; // z 是 0
  • ??= 是 ?? 运算符的赋值形式,用于在左侧操作数为 null 或 undefined 时,将右侧操作数赋值给左侧操作数。
    示例:
let a = null;
a ??= 'default'; // 现在 a 的值是 'default'
let b = 'value';
b ??= 'default'; // b 的值不会改变,是 'value'
3、使用forEach方法

要根据每个元素的父子关系重新构建数据结构
假设有一个扁平化数组,每个元素包含 id 和 parent_id 字段来表示父子关系,树形数组的每个节点包含 id 和 children 字段。

function buildTree(flatArray) {
    // 创建一个映射表,用于以id为键快速查找节点
    let idMap = {};
    flatArray.forEach(item => {
        idMap[item.id] = { ...item, children: [] }; // 深拷贝每个item,并初始化children为空数组
    });
    let tree = [];
    flatArray.forEach(item => {
        if (item.parent_id !== null && idMap[item.parent_id]) {
            // 如果不是根节点且父节点存在,则将当前节点添加到父节点的children数组中
            idMap[item.parent_id].children.push(idMap[item.id]);
        } else {
            // 如果是根节点,直接添加到树的顶层
            tree.push(idMap[item.id]);
        }
    });
    //返回构建好的 tree 数组,其中包含了树的根节点和各层级的子节点。
    return tree;
}
// 示例数组
let flatArray = [
    { id: 1, parent_id: null },
    { id: 2, parent_id: 1 },
    { id: 3, parent_id: 1 },
    { id: 4, parent_id: 2 },
    { id: 5, parent_id: 3 },
    { id: 6, parent_id: null },
    { id: 7, parent_id: 6 },
];
// 转换成树形数组
let treeArray = buildTree(flatArray);
console.log(treeArray);
4. 迭代方法

迭代方法通常使用循环和字典(或者 Map)来实现。它适用于处理层级不深、数据量较大的情况。

function buildTree(arr) {
  const tree = {};
  const map = {};
  arr.forEach(node => {
    map[node.id] = { ...node, children: [] };
  });
  Object.values(map).forEach(node => {
    if (node.parent_id) {
      map[node.parent_id].children.push(node);
    } else {
      tree[node.id] = node;
    }
  });
  return Object.values(tree);
}
5.使用递归和映射(Map)

结合递归和 Map 对象,可以避免多次遍历和条件判断,提高效率。

function buildTree(arr) {
  const map = new Map();
  const tree = [];
  arr.forEach(node => map.set(node.id, { ...node, children: [] }));
  map.forEach(node => {
    if (node.parent_id) {
      const parent = map.get(node.parent_id);
      if (parent) {
        parent.children.push(node);
      }
    } else {
      tree.push(node);
    }
  });
  return tree;
}
6.使用 reduce 函数

利用数组的 reduce 方法,可以更紧凑地实现树的构建过程。

function buildTree(arr) {
  const map = {};
  const tree = [];
  arr.reduce((acc, node) => {
    map[node.id] = { ...node, children: [] };
    if (node.parent_id === null) {
      tree.push(map[node.id]);
    } else {
      const parent = map[node.parent_id];
      if (parent) {
        parent.children.push(map[node.id]);
      }
    }
    return acc;
  }, []);
  return tree;
}
7.使用深度优先搜索(DFS)

深度优先搜索是一种递归的算法,通过遍历数组并递归构建树,可以有效地将扁平化数组转换为树形结构。

function buildTree(arr) {
  const tree = {};
  const dfs = (parentId) => {
    return arr
      .filter(node => node.parent_id === parentId)
      .map(node => ({ ...node, children: dfs(node.id) }));
  };
  tree.children = dfs(null);
  return tree.children;
}
8.使用广度优先搜索(BFS)

广度优先搜索可以通过迭代实现,使用队列来逐层构建树形结构,适合于需要按层级构建树的情况。

function buildTree(arr) {
     const tree = []; // 初始化树形结构为一个空数组
     const queue = [{ id: null, children: tree }];
     while (queue.length > 0) {
       const { id, children } = queue.shift();
          arr.forEach((node) => {
          if (node.parent_id === id) {
              const newNode = { ...node, children: [] }; // 每个节点的 children 初始化为空数组
              children.push(newNode); // 将新节点添加到父节点的 children 数组中
              queue.push(newNode); // 将新节点加入队列,继续处理其子节点
            }
          });
        }
        return tree; // 返回整个树的数组形式
      }

总结

提示:这里对文章进行总结:
递归 vs 迭代:递归方法通常直观且易于理解,但在处理大量数据时可能会有性能上的挑战,可能面临堆栈溢出或性能问题。迭代方法和使用 Map 的方法通常可以提供更好的性能,特别是在数据量较大或层级较深的情况下,通常更适合处理大数据量和层级深的情况。选择合适的方法可以根据实际需求进行权衡和调整。
DFS vs BFS:DFS适合递归实现,BFS适合迭代实现。选择取决于数据结构和需求,例如是否需要按层级构建树。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值