好友的求助
如上图当我准备给对手一记OTK时,我的微信好友向我发起了求救,他说宝,能帮我写一个算法吗?乐于助人的我打开了这个js文件,上面给着如下的输入输出,当我看到children
,我就知道这是一个简单的平铺数组结构转树形结构的题目。
其中,他提出了一个0级元素的概念,我觉得可以研究一下,所以产出这篇文章。如有🙅,敬请指正!
数据源分析确定
以下所有的例子都是根据下面的2个变量:dataIn输入、dataOut输出:
平铺数组结构转为树形结构
确定顶层ID的情况(通常情况)
一般情况下,给定我们需要转化的数组他一定有若干项是处于第一层
的,如下图所示。通常来说,研发部和人力资源部是处于第一级的,总部是处于第0级
的,因为总部通常是虚拟节点,它的存在作用是确定节点是否是第一级节点
。
通常来说
第0级节点
的id值为0
或者null
,则判断第一级节点的条件为node.parentId===0||node.parentId===null
递归写法
对于可以抽象成指定规则的算法题我们可以使用递归,当然递归由于其需要不断的压栈出栈所以性能会有损耗。
其实想要真正理解递归并不容易,因为递归本来就是给计算机读的。要想写好递归,一定要将递归的逻辑抽出来为一个整体,那么我们这道题抽离递归的逻辑是什么呢?我把这个过程归纳为这二步:
第一步.筛选第一级菜单 第二步.加入子菜单
注意这个第一级不是针对整体来说的 而是每一个递归过程的第一步。
- 筛选一级菜单
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数组中了。
至此,我们已经完成了递归函数的第一步 筛选一级菜单。
- 给一级菜单添加子菜单
前面我们已经添加了一级菜单,那么如何添加子菜单呢?
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;
}
- 删除多余的
children
元素
我们上节中给所有item都添加了children元素
,但是叶子元素是没有children
的所以我们需要给他去除
if(item.children.length===0){
delete item.children;
}
非递归写法
非递归写法的思路也很简单,主要是借助了对象的引用,直接将值存在了Map
上,而result存储的是Map的值
.
在第16行中,treeItem存储的是map,也就是对象的引用,而又将引用push进了result,所以每当map中的值变化,result也会变化。
不确定顶层ID的情况(本文重点讨论情况)
上节我们说到一般情况下,是明确了第0层节点的id值为0
,但是也有可能我们并不知道第0级节点的id是多少
,也就是说我们不知道第一级节点的parentId
是多少,那么我们怎么进行计算呢?我们的数据还是以上文为准,但是此时我们并不知道他的父节点是多少,即无法根据parentId===0来进行判断;
- 首先定义了
parentIdMap和idMap
来存储对应的key=parentId、key=id
的map结构 - 先遍历整个数组将
parentIdMap和idMap
都填充上值 - 将所有id取出放到一个数组
idMapArr
中
当数据的children树形全部填充好以后,那么如何区分哪些是第一级节点呢?答案就是parents,因为光凭children树形并不能区分哪一个是第一级节点,因为第一级节点和第二级甚至层级更深的树都有children属性,所以我们需要增加parents属性,parents属性代表他的父节点,当parents变量为空时,就代表这些数据是第一级节点
- 增加
children属性和parents
属性 - 最后筛选出哪一个数组里面的
parents
节点为空
如果有更好的方法 欢迎留下你的想法 一起学习 交流 进步
树形结构转为平铺数组
树形结构转为平铺数组比较简单,不需要考虑
id
以及parentId
的关系,只需要考虑children
。
递归版
递归版 比较简单唯一需要注意的是在forEach时就需要将item push到数组中(第三行)
非递归版
本文如有错误🙅♂️ 请及时纠正 或者有更好的方法 可以在评论区留言 本文会采纳并更新