JS面试手写:一维数组转换为树形数据结构

737 阅读1分钟

将一个形如下面的对象数组转换成一个树形结构的数组

let dataArr = [
 { id: 1, pid: 0, name: "a" },
  { id: 2, pid: 1, name: "a_1" },
  { id: 3, pid: 1, name: "a_2" },
  { id: 4, pid: 2, name: "a_1_1" },
  { id: 5, pid: 3, name: "a_1_2" },
]

方法一:递归

const arrToTree = (arr, tree, pid) => {
  arr.forEach((item) => {
    // 判断是否为父级菜单
    if (item.pid === pid) {
      const child = {
        ...item,
        children: [],
      };
      // 迭代 arr, 找到当前菜单相符合的所有子菜单
      arrToTree(arr, child.children, item.id);
      // 删掉不存在 children 值的属性
      if (child.children.length <= 0) {
        delete child.children;
      }
      // 加入到树中
      tree.push(child);
    }
  });
};
// 调用函数时的参数为:1.原一维数组;2.最后获取的树状数组的引用;3.根id
let resTree = [] 
arrToTree(dataArr, resTree, 0)

方式二:reduce递归(时间复杂度同上,代码简洁一些)

const  = (arr, pid) => {
  return arr.reduce((res, current) => {
    if (current['pid'] === pid) {
      current.children = arrayToTree(arr, current['id']);
      return res.concat(current);
    }
    return res;
  }, []);
};
// 调用函数时的参数为:1.原一维数组;2.根id
let resTree = arrayToTree(data, 0);

上面两种方式可以获得一个一维数组转换为树形数据结构,但是有着一些缺陷:1.时间复杂度过高(O(n²)) 2.如果从根节点出发,其他数据节点没有形成一棵树的关系,那么最后只会返回根节点。

比如:

const data = [
  { id: 1, pid: 0, name: "a" },
  { id: 2, pid: 8, name: "a_1" },
  { id: 3, pid: 9, name: "a_2" },
  { id: 4, pid: 10, name: "a_1_1" },
  { id: 5, pid: 11, name: "a_1_2" },
];

// 方式一:
let resTree = [] 
arrToTree(dataArr, resTree, 0) // resTree只会为:[ { id: 1, pid: 0, name: 'a' } ]

// 方式二:
let resTree = arrayToTree(data, 0); // resTree只会为:[ { id: 1, pid: 0, name: 'a', children: [] } ]

方式三:map + 遍历

function arrayToTree(arr) {
  let obj = {};
  // obj对象的key为arr中每一个对象的id,value为每一个对象
  arr.map((item) => {
    obj[item.id] = item;
  });
  
  // 最终要返回的树型数组
  let newArr = [];
  
  // 对原一维数组进行遍历
  for (let i = 0; i < arr.length; i++) {
    let item = arr[i]; // 原一维数组中的每一项
    let parent = obj[item.pid]; // 从之前保存的对象中取出当前项的父项
    if (parent) {
      if (parent.children) {
        parent.children.push(item); // 父项的children加入子项
      } else {
        parent.children = [];
        parent.children.push(item);
      }
    } else {
      newArr.push(item);   // 否则直接将当前项加入最后的树状数组作为根(因为此项没有父项)
    }
  }
  return newArr;
};
let resTree = arrayToTree(data);

此时时间复杂度降为O(n),且没有父项的节点都会作为根节点