高频面试题:树结果转为数组,数组转为树(详细解答,性能提升之王)

100 阅读5分钟

想起博主前几个月的面试,不免感叹,还真是愿意考东西~,话不多说,咱们进入正题!

一、数组转为树结构

1、递归(大法好)

const list = [{
        "id": 18,
        "parentId": 16
      }, {
        "id": 19,
        "parentId": 0
      },
      {
        "id": 16,
        "parentId": 0
      },
      {
        "id": 20,
        "parentId": 19
      },
      {
        "id": 17,
        "parentId": 16
      },

      {
        "id": 22,
        "parentId": 17
      },
      {
        "id": 21,
        "parentId": 18
      },
      {
        "id": 23,
        "parentId": 22
      },
      {
        "id": 24,
        "parentId": 23
      }
      ];
      function arrayToTree(arr, pid) {
        let res = [];
        arr.forEach((item) => {
          // 等于0即为根节点
          if (item.parentId === pid) {
            // 一层层遍历 直到不是 根节点为止
            item.children = arrayToTree(arr, item.id);
            res.push(item);
          }
        });
        return res;
      }
      console.log('arrayToTree', arrayToTree(list, 0));

  • 虽然递归的代码简洁且容易理解,但递归的时间复杂度有点高;

2、数组转为树结构(双层循环)

// id相匹配的元素是父子级关系

const list = [{
        "id": 18,
        "parentId": 16,
      }, {
        "id": 19,
        "parentId": 0,
      },
      {
        "id": 16,
        "parentId": 0,
      },
      {
        "id": 20,
        "parentId": 19
      },
      {
        "id": 17,
        "parentId": 16,
      },

      {
        "id": 22,
        "parentId": 17,
      },
      {
        "id": 21,
        "parentId": 18,
      },
      {
        "id": 23,
        "parentId": 22,
      },
      {
        "id": 24,
        "parentId": 23,
      },
    ];
    
    
    // 初级解法(容易想到)下面会单独拆解该方法 ~ qiudemadai
    function arrayToTree(array) {
    
      // 创建一个映射对象
      const obj = {};
      array.forEach(item => {
        // 为数组初始化一个children = []
        item.children = []
        obj[item.id] = item;
      });
      
      // 创建一个空数组,用于存放树节点
      const tree = [];
      array.forEach(item => {
        // 获取父节点
        const parent = obj[item.parentId];
        // 如果有父节点
        if (parent) {
          // 将当前元素推入父节点的children数组
          parent.children.push(item);
        } else {
          // 如果没有父节点,就说明是根节点,推入tree数组
          tree.push(item);
        }
      });
      // 返回tree数组
      return tree;
    }

    // 调用函数,打印结果
    console.log(arrayToTree(list));

解析-(双层循环))

const obj = {};
    // 第一等循环的作用是 对obj对象赋值, 将obj对象改造成包含 id,parentId,children的数据格式,
    // 方便后面在第二层循环取值使用
    array.forEach(item => {
    // 为数组初始化一个children = []
    item.children = []
    // 用数组中的id 为对象的key,对象的Value就是数组中的每一项
    obj[item.id] = item;
    });
      
  • obj[item.id] = item的含义是:将obj用数组中的id 为对象的key,obj 的 value 就是 list 数组中的每一项,再加上前面在循环中的item.children = [],obj的大概数据格式如图:key: item.id;value: 三项组成id,parentId,children

image.png

  • 然后进入第二层循环,第二层循环中首先创建了一个数组 tree ,用来存储树节点,const parent = obj[item.parentId]这个行代码需要认真理解,这行代码的意思是用 list 数组中每一项的parentId,在 obj 对象中进行匹配,这就是我上面说的用处,注意这里面有两项({ "id": 19, "parentId": 0, }, { "id": 16, "parentId": 0, },)的parentId是0,所以我们可以根据 parentId 是否为0,来区分是否为根节点,如果是根节点,则直接将节点push,否则将parent.children放进去。
 // 创建一个空数组,用于存放树节点
      const tree = [];
      array.forEach(item => {
        // 获取父节点
        const parent = obj[item.parentId];
        console.log('papaa----parent', parent);
        // 如果有父节点
        if (parent) {
          // 将当前元素推入父节点的children数组
          parent.children.push(item);
        } else {
          // 如果没有父节点,就说明是根节点,推入tree数组
          tree.push(item);
        }
      });
  • const parent = obj[item.parentId]这行代码执行完成后,大致样子如图,我做了一个匹配,帮助大家理解。

image.png

  • 最后可以看见这个树结构的匹配性是很高的,可以多层树结构嵌套~如图

image.png

3、数组转为树结构(单层循环)

  • 回想刚刚的代码,利用了双层for循环,比较容易理解,但是仔细想一下 是不是第一层for(为obj放id,parentId,children)这一步可以也放到一层循环中,改造如下:
const list = [{
        "id": 18,
        "parentId": 16
      }, {
        "id": 19,
        "parentId": 0
      },
      {
        "id": 16,
        "parentId": 0
      },
      {
        "id": 20,
        "parentId": 19
      },
      {
        "id": 17,
        "parentId": 16
      },

      {
        "id": 22,
        "parentId": 17
      },
      {
        "id": 21,
        "parentId": 18
      },
      {
        "id": 23,
        "parentId": 22
      },
      {
        "id": 24,
        "parentId": 23
      }
      ];
    
    
      function arrayToTree(list) {
        const result = []; // 存放结果
        const current = {};
        for (const item of list) {
          const id = item.id;
          const parentId = item.parentId;

          // 没有这个属性 就创建一个children 初始化为空
          if (!current[id]) {
            current[id] = {
              children: []
            };
          }
          current[id] = Object.assign(item, current[id]);
          console.log('current', current);
          const treeItem = current[id];
          if (!parentId) {
          // 根节点
            result.push(treeItem);
          } else {
          // 如果发现是children 并且没有找到对应父级的 parentId, 就初始化父级的children
            if (!current[parentId]) {
              current[parentId] = {
                children: []
              };
            }
            current[parentId].children.push(treeItem);
          }
        }
        return result;
      }
      console.log('arrayToTree', arrayToTree(list));

解析-(单层循环))

  • 同样我打印了对象current的值,结果如下,看起来效果没差,但却减少了一层循环。

image.png

  • 输出结果如下,也是一点没差,看来单层循环已经足够!这里补充下为什么不直接来单层循环的写法,因为我个人觉得双层循环是我很容易想到的,明白了双层循环的含义,才能更好的理解单层循环~

image.png

二、树结构转为数组

1、递归大法好

 const tree = [
    {
      "id": 16,
      "parentId": 0,
      "children": [
        {
          "id": 18,
          "parentId": 16,
          "children": []
        },
        {
          "id": 17,
          "parentId": 16,
          "children": []
        }
      ]
    },
    {
      "id": 19,
      "parentId": 0,
      "children": [
        {
          "id": 20,
          "parentId": 19,
          "children": [
            {
              "id": 21,
              "parentId": 20,
              "children": [
                {
                  "id": 22,
                  "parentId": 21,
                  "children": [
                    {
                      "id": 23,
                      "parentId": 22,
                      "children": [
                        {
                          "id": 24,
                          "parentId": 23,
                          "children": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ];
function treeToArray(tree) {
    let res = [] 
      for (const item of tree) {
        const { children, ...i } = item 
        /// i是对 item的重新命名
          if (children && children.length) {
            res = res.concat(treeToArray(children))
            } 
           res.push(i)
        } 
      return res
    }

2、队列


const tree = [
        {
          "id": 16,
          "parentId": 0,
          "children": [
            {
              "id": 18,
              "parentId": 16,
              "children": []
            },
            {
              "id": 17,
              "parentId": 16,
              "children": []
            }
          ]
        },
        {
          "id": 19,
          "parentId": 0,
          "children": [
            {
              "id": 20,
              "parentId": 19,
              "children": [
                {
                  "id": 21,
                  "parentId": 20,
                  "children": [
                    {
                      "id": 22,
                      "parentId": 21,
                      "children": [
                        {
                          "id": 23,
                          "parentId": 22,
                          "children": [
                            {
                              "id": 24,
                              "parentId": 23,
                              "children": []
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ];
      function flattenTree(tree) {
        // 创建空数组 存放扁平化结果
        const result = [];
       
        // 将根节点或整棵树的节点放入queue中  
        const queue = [...tree];
 
        while (queue.length) {
          // 从队列queue中取出队首元素
          const node = queue.shift();
          result.push({
            id: node.id,
            parentId: node.parentId
          });
 
          if (node.children) {
            // ...node.children 就是相当于取出数组中的每一项放到queue中
            queue.push(...node.children);
          }
        }
 
        return result;
      }
      const flattenedTree = flattenTree(tree);
      console.log(flattenedTree);