本文内容结构大概如下:
let tree = [
{
id: '1',
title: '节点1',
children: [
{
id: '1-1',
title: '节点1-1'
}
]
},
{
id: '2',
title: '节点2',
children: [
{
id: '2-1',
title: '节点2-1'
},
{
id: '2-2',
title: '节点2-2'
}
]
},
]
1.树结构遍历
1.1 广度优先遍历
节点1,节点2,节点1-1,节点2-1,节点2-2,先遍历父节点,再遍历子节点
function scopeTree(tree, func) {
let node, list = [...tree]
while (node = list.shift()) {
func(node)
node.children && list.push(...node.children)
}
}
scopeTree(tree, node => { console.log(node.title) })
1.2 深度优先遍历
1.2.1.先序遍历(节点1,节点1-1,节点2,节点2-1,节点2-2,先遍历一个节点及其子节点,再遍历另一个节点及其子节点,以此类推)
function deepTree(tree, func) {
tree.forEach(data => {
func(data)
data.children && deepTree(data.children, func)
})
}
deepTree(tree, node => { console.log(node.title) })
1.2.2后序遍历(节点1-1,节点1,节点2-1,节点2-2,节点2,先遍历完一个子节点及其父节点,再遍历另一个子节点及其父节点)
function deepTreeLater(tree, func) {
tree.forEach(data => {
data.children && deepTreeLater(data.children, func)
func(data)
})
}
deepTreeLater(tree, node => { console.log(node.title) })
2.列表结构和树结构相互转换
2.1 列表转树结构
const list = [
{
id: '1',
title: '节点1',
parentId: '',
},
{
id: '1-1',
title: '节点1-1',
parentId: '1'
},
{
id: '2',
title: '节点2',
parentId: ''
},
{
id: '2-1',
title: '节点2-1',
parentId: '2'
},
{
id: '2-2',
title: '节点2-2',
parentId: '2'
},
]
2.1.1 双层for循环实现
// 此处是数组对象里的id跟数组里另外一个对象里的parentId进行比较,
// 不同于排序时数组里单个值之间相互比较,
// 无法简单的用length-1这种不用和自己比较的操作
function listToTree1(list) {
for (var i = 0; i < list.length; i++) {
// console.log("外部")
for (var j = 0; j < list.length; j++) {
// console.log("内部");
// 自己的id不用跟自己的parentId比较
if (i == j) continue;
if (list[i].id == list[j].parentId) {
if (!list[i].children) {
list[i].children = [];
}
list[i].children.push(list[j]);
}
}
}
return list.filter(item => !item.parentId);
}
let value1 = listToTree1(list);
console.log("双层for循环遍历:", value1);
2.1.2 reduce实现
function listToTree2(list) {
let info = list.reduce((pre, item) => (pre[item.id] = item, item.children = [], pre), {})
return list.filter(item => {
info[item.parentId] && info[item.parentId].children.push(item)
return !item.parentId
})
}
let value2 = listToTree2(list)
console.log("reduce遍历:", value2);
2.2 树结构转列表
//递归实现
function treeToList (tree, result = [], level = 0) {
tree.forEach(node => {
result.push(node)
node.level = level + 1
node.children && treeToList(node.children, result, level + 1)
})
return result
}
const res = treeToList(tree)
console.log(res);
1.添加父子级节点关系
function treeToList(tree, result = [], level = 0, time = 0) {
tree.forEach((node, index) => {
result.push(node);
node.level = level + 1;
node.parentIndex = time
if (node.children) {
treeToList(node.children, result, level + 1, time);
// time判断自身父级所属位置
time += 1
}
});
return result;
}
2.获取到某一层级的树结构
//假如获取树的第一层级
this.level = 1;
let newTree = treeToList(tree).filter((item) => item.level == level);
//删除第一层之外的其他数据
function getTreeLevel(tree, level) {
let arr = tree.map((node) => ({ ...node }))
arr.map((item) => {
item.children && getTreeLevel(item.children, level);
if (item.children && item.level == level) {
delete item.children;
}
});
return arr
}
let finalTree = getTreeLevel(newTree, this.level);
console.log(finalTree);
3.树结构筛选
function treeFilter(tree, func) {
// 使用map复制一下节点,避免修改到原树
return tree.map(node => ({ ...node })).filter(node => {
node.children && (node.children = treeFilter(node.children, func))
return func(node) || (node.children && node.children.length)
})
}
4.树结构查找
4.1. 查找节点
function treeFind(tree, fn) {
for (const item of tree) {
if (fn(item)) {
return item;
}
if (item.children) {
const res = treeFind(item.children, fn);
if (res) return res;
}
}
}
const res = treeFind(tree,node => node.id == '2-1')
4.2. 查找节点路径(回溯法)
function treeFindPath(tree, func, path = []) {
if (!tree) return []
for (const data of tree) {
// 假设满足条件,直接放到数组里
path.push(data.title)
if (func(data)) {
return path
}
if (data.children) {
const res = treeFindPath(data.children, func, path)
// 只有当数组的长度大于0才返回值
if (res.length) return res
}
// 条件都不满足,则直接删除,对应前面的push
path.pop()
}
return []
}
let result = treeFindPath(tree, node => node.id === '2-1')
console.log(result) //['节点2', '节点2-1']
4.3 查找树结构最里层的第一个子节点
function getFirstChild(list) {
let result = JSON.parse(JSON.stringify(list));
while (result[0].children) {
result = result[0].children;
}
return result[0];
}
4.4 树结构最里层的每一个子节点组成新的数组
function lookForAll(data, arr = []) {
for (let item of data) {
if (item.children && !item.children.length) {
arr.push(item);
}
if (item.children && item.children.length) {
lookForAll(item.children, arr);
}
}
return arr;
}
5.结语
对于树结构的操作,递归是最基础的,也是最容易理解的。用循环能实现的,递归一般可以实现,但是能用递归实现的,循环不一定能。