开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情。
作为一个开发人员,难免会处理各种各样的数据。最常见的数据处理就是遍历和递归了。
最常见的遍历一般使用 for 循环或者 map/forEach 这些方法就可以实现,但是这些还不足以解决所有的场景。
本篇文章记录一下常见数据递归的两种常见场景:广度优先和深度优先。
数据准备
先准备一套树状结构数据,如下所示:
const data = [
{
id: 1,
name: '0',
children: [
{ id: 2, name: '0-1', children: [{ id: 6, name: '0-1-1' }] },
{ id: 7, name: '0-2', children: [{ id: 8, name: '0-2-1' }] },
],
},
{
id: 3,
name: '1',
children: [{ id: 4, name: '1-1', children: [{ id: 5, name: '1-1-1' }] }],
},
]
广度优先
广度优先是相对于深度优先来说的,简单理解一下就是,一层一层的遍历。
例如上面的数据,我们在根下有两个对象,首先参与遍历的就是 id 为 1 和 3 的两条数据,然后是 2/7/4 这样,从上到下一层一层的遍历,直到所有的 children 都照顾到,就算完成了一次遍历。
适用的场景按照一个地图顺序去查找一条数据,例如按照 1-2-6 的顺序去拿到 '0-1-1' 这条数据,我们就需要逐次的去匹配 id,然后拿到数据。
具体方法如下:
/**
* 广度优先遍历方法
* @param {an[]} tree 原始树状数据
* @param {Function} func 回调函数
* @returns 递归结果
*/
function treeForeach(tree, func) {
let node /** 临时节点 */,
list = [...tree] /** 展开运算符,展开节点 */,
result = []; /** 最终返回结果 */
// 遍历体
while ((node = list.shift())) {
// 执行回调方法
// func(node)
// 临时创建一个节点,保存在结果中
let tmpNode = {
id: node.id,
name: node.name,
};
result.push(tmpNode);
// 改变list,使得 while 条件进行下去
node.children && list.push(...node.children);
}
// 返回结果
return result;
}
const result = treeForeach(data);
console.log(result);
深度优先
如果说广度优先是一层一层的话,深度优先则是一个一个的来。
还是这套数据,我们会先从 id为1的数据开始,然后到 2 再到 6,完成第一个遍历,然后返回到 7-8 这样子去遍历数据。
使用场景的话,是比较适合去搜索第一个满足条件的数据,例如遍历权限,找到第一个 admin ,就可以退出遍历这样的场景。
贴出方法:
/**
* 深度优先遍历
* @param {object || any[]} node 递归数据
* @param {any[]} nodeList 保存最终结果
* @returns any[] nodeList
*/
const deepForeach = (node, nodeList = []) => {
// 如果有node节点则执行主逻辑
if (node) {
// 导出结果填入节点数据
nodeList.push({ id: node.id, name: node.name });
// 如果有数据且有数据长度则继续递归
node.children &&
node.children.length &&
node.children.forEach((item) => deepForeach(item, nodeList));
}
return nodeList;
};
console.log(data.map((item) => deepForeach(item)));
后记
上面这两个方法是看了一些前辈大佬们写的方法,又简单整理了一下才作为工具方法进行记录在这里的,其实只是核心思路。
不论是广度优先还是深度优先,其实都是我们解决实际开发问题的一种方案,实际工作中,还是会结合实际数据结构来选择采用的具体方案。
希望能对大家有些帮助。