🔥史上最全的树形结构-平铺数组互转,不接受反驳

3,587 阅读5分钟

好友的求助

1.png

如上图当我准备给对手一记OTK时,我的微信好友向我发起了求救,他说宝,能帮我写一个算法吗?乐于助人的我打开了这个js文件,上面给着如下的输入输出,当我看到children,我就知道这是一个简单的平铺数组结构转树形结构的题目。

image.png

image.png

其中,他提出了一个0级元素的概念,我觉得可以研究一下,所以产出这篇文章。如有🙅,敬请指正!

数据源分析确定

以下所有的例子都是根据下面的2个变量:dataIn输入、dataOut输出:

2.png

平铺数组结构转为树形结构

确定顶层ID的情况(通常情况)

一般情况下,给定我们需要转化的数组他一定有若干项是处于第一层的,如下图所示。通常来说,研发部和人力资源部是处于第一级的,总部是处于第0级的,因为总部通常是虚拟节点,它的存在作用是确定节点是否是第一级节点

1651261241443.jpg

通常来说第0级节点的id值为0或者null,则判断第一级节点的条件为node.parentId===0||node.parentId===null

递归写法

对于可以抽象成指定规则的算法题我们可以使用递归,当然递归由于其需要不断的压栈出栈所以性能会有损耗。

其实想要真正理解递归并不容易,因为递归本来就是给计算机读的。要想写好递归,一定要将递归的逻辑抽出来为一个整体,那么我们这道题抽离递归的逻辑是什么呢?我把这个过程归纳为这二步:第一步.筛选第一级菜单 第二步.加入子菜单注意这个第一级不是针对整体来说的 而是每一个递归过程的第一步。

  1. 筛选一级菜单
let item;
 
const getTreeList=(dataSource,parentId,list,{
        idName,parentIdName,childrenName
})=>{
    for(item of dataSource){
        //遍历每一项,每当当前遍历项的parentId===传进来的parentId时 说明 当前项是parentList的子项
        //如果还是不理解 可以想象当前的传入的parentId为0 此时往parentList push的就是第一级的item
        if(item[parentIdName]===parentId){
            list.push(item);
        }
    } 
    console.log(list);
    //[{id: 1, parentId: 0, name: '研发部'},{id: 6, parentId: 0, name: '人力资源部'}]
}


const ArrayToTree=(dataSource,parentId,{
    idName='id',parentIdName='parentId',childrenName='children'
}={})=>{
    return getTreeList(dataSource,parentId,[],{
        idName,parentIdName,childrenName
    });
}

ArrayToTree(dataIn,0) 

我们将dataIn和0传递进去,打印出list,我们可以看到第一级部门研发部和人力资源部已经进去了list数组中了。

至此,我们已经完成了递归函数的第一步 筛选一级菜单。

  1. 给一级菜单添加子菜单

前面我们已经添加了一级菜单,那么如何添加子菜单呢?

const getTreeList=(dataSource,parentId,list,{
        idName,parentIdName,childrenName
})=>{
    ...
    for(item of list){
        item.children=[];
        //采用递归的方式给一级菜单添加子菜单
        getTreeList(dataSource,item.id,item.children,{
            idName,parentIdName,childrenName
        });
    }
    return list;
}
  1. 删除多余的children元素

我们上节中给所有item都添加了children元素,但是叶子元素是没有children的所以我们需要给他去除

if(item.children.length===0){
    delete item.children;
} 

3.png

非递归写法

非递归写法的思路也很简单,主要是借助了对象的引用,直接将值存在了Map上,而result存储的是Map的值.

4.png

在第16行中,treeItem存储的是map,也就是对象的引用,而又将引用push进了result,所以每当map中的值变化,result也会变化。

不确定顶层ID的情况(本文重点讨论情况)

上节我们说到一般情况下,是明确了第0层节点的id值为0,但是也有可能我们并不知道第0级节点的id是多少,也就是说我们不知道第一级节点的parentId是多少,那么我们怎么进行计算呢?我们的数据还是以上文为准,但是此时我们并不知道他的父节点是多少,即无法根据parentId===0来进行判断;

5.png

  1. 首先定义了parentIdMap和idMap来存储对应的key=parentId、key=id的map结构
  2. 先遍历整个数组将parentIdMap和idMap都填充上值
  3. 将所有id取出放到一个数组idMapArr

当数据的children树形全部填充好以后,那么如何区分哪些是第一级节点呢?答案就是parents,因为光凭children树形并不能区分哪一个是第一级节点,因为第一级节点和第二级甚至层级更深的树都有children属性,所以我们需要增加parents属性,parents属性代表他的父节点,当parents变量为空时,就代表这些数据是第一级节点

  1. 增加children属性和parents属性
  2. 最后筛选出哪一个数组里面的parents节点为空

如果有更好的方法 欢迎留下你的想法 一起学习 交流 进步

树形结构转为平铺数组

树形结构转为平铺数组比较简单,不需要考虑id以及parentId的关系,只需要考虑children

递归版

递归版 比较简单唯一需要注意的是在forEach时就需要将item push到数组中(第三行)

7.png

非递归版

carbon (8).png

本文如有错误🙅‍♂️ 请及时纠正 或者有更好的方法 可以在评论区留言 本文会采纳并更新