谁还不会树形结构遍历? 一文通晓
背景
昨天做了个需求, 关于element级联选择器的,后端要求只要叶子节点的数据,且只返回叶子节点的数据,需要前端遍历树取父级节点数据,来回显节点数据。(阿伟, 我不能直接给你么???????????)
案例结构
js
let tree = [
{
id: 'a',
children: [
{
id: 'b',
children: [
{
id: 'c',
children: null
}
]
},
{
id: 'd',
children: [
{
id: 'e',
children: null
}
]
},
{
id: 'f',
children: [
{
id: 'g',
children: null
}
]
}
]
},
{
id: 'h',
children: [
{
id: 'i',
children: [
{
id: 'j',
children: null
}
]
},
{
id: 'k',
children: [
{
id: 'l',
children: null
}
]
},
{
id: 'm',
children: [
{
id: 'n',
children: null
}
]
}
]
}
]
输入/输出
级联选择器选择叶子节点后, 后台返回叶子节点数据处理成回显的数据.(多选)
输入 | 输出 |
---|---|
['c','e'] | [['a','b','c'],['a','d','e']] |
树形遍历
深度递归遍历
- 递归遍历到目标节点
- 节点入栈
- 函数出栈, 节点入栈
- 直到函数作用结束
js
findIdsByValue(root, id, stack) {
if (!root) return;
if (Array.isArray(root)) {
for (const iterator of root) {
if (iterator.id === id) {
stack.unshift(iterator.id);
return stack;
}
if (iterator.children && Array.isArray(iterator.children)) {
const target = this.findIdsByValue(
iterator.children,
id,
stack
);
if (target) {
stack.unshift(iterator.id);
return target;
}
}
}
}
}
这里已经能够解决需求了, 然后将 findIdsByValue 函数放入目标循环体中, 循环出目标数组
优化遍历
在数据选择较多的情况下, 上述方法会浪费算力, 重新走重复的路线, 我的思路是能不能深度递归的同时, 把当前的父级路径都记录下来, 然后根据已知的回显数组来判断是否收集当前的路径.
js
function findLabelByValues(root, ids) {
let stack = [];
let targetArr = [];
if(Array.isArray(ids)) {
throw new TypeError('argument type error');
}
return recursion(root, ids, stack,targetArr);
function recursion(root, ids, stack,targetArr) {
if (!root) return;
if (Array.isArray(root)) {
for (let index = 0; index< root.length; index++ ) {
let iterator = root[index];
// 同级出栈
if(index > 0) {
stack.pop();
}
stack.push(iterator.id);
let i = ids.findIndex(item => item == iterator.id);
if(i > -1){
ids[i] = null;
// targetArr.push([...stack]);
// 保证输出顺序
targetArr[i] = [...stack];
if(ids.findIndex(item=> item !== null) === -1) {
return targetArr;
}
}
if (iterator.children && Array.isArray(iterator.children)) {
let target = recursion(
iterator.children,
ids,
stack,
targetArr
);
if(target) {
return target;
}
stack.pop();
}
}
}
}
}
这样就完成了一次性的路径收集了, 并且一次深度遍历就能收集所有的路径了, 不用重复的递归
广度优先遍历
完成了深度优先的写法, 我也试着写了广度优先的写法
js
function findLabelByValueMore(data, id) {
let quene = [];
let resultStack = [];
// 首先将树元素入队列
quene = data;
// 直到队列元素清空
// shift() 方法经常用于 while 循环的条件中。下例中每次迭代都会从一个数组中移除下一项元素,直至它成为空数组。
while(quene.length) {
// 删除数组第一个值, 并且返回该元素的值, 如果数组为空就返回undefined, 出队列
let item = quene.shift();
// 取子节点
let child = item.children;
// 判断取值
if(id === item.id) {
resultStack.unshift(item.id);
let up = item.parent;
// 遍历取值
while(up) {
resultStack.unshift(up.id);
up = up?.parent;
}
return resultStack;
}
// 遍历子元素进队列
if(child && Array.isArray(child)) {
for (let index = 0; index < child.length; index++) {
const element = child[index];
quene.push(element);
// 将父级元素记录在当前元素里面
element.parent = item;
}
}
}
}
关键点是需要记录父节点
总结
上述代码, 未处理边界条件, 和参数限制等, 文中有什么问题希望大神不吝赐教.