提示:以下是本篇文章正文内容,下面案例可供参考
提示:将扁平化数组转换成树形数组
1、使用递归的方法
//定义一个buildTree函数,它接受一个数组和一个可选的parentId参数
//parentId默认为null,表示从根节点开始构建树。
function buildTree(arr, parentId = null) {
const result = [];
const temp = arr.filter(item => item.parent_id=== parentId);
temp.forEach(item => {
//对于每个节点,递归调用buildTree函数来构建其子树,并将结果赋值给item.children。
const children = buildTree(arr, item.id);
if (children.length) {
item.children = children;
}
result.push(item);
});
//将当前节点添加到结果数组中。最终,返回结果数组作为树的根节点。
return result;
}
// 示例数组
const arr = [
{id: 4, parent_id: 3},
{id: 'aa', parent_id: 'a'},
{id: 1, parent_id: null},
{id: 3, parent_id: 2},
{id: 'a', parent_id: 'a0'},
{id: 2, parent_id: 1},
{id: 'a0', parent_id: null}
];
const tree = buildTree(arr);
console.log(JSON.stringify(tree, null, 2));
2、使用reduce 方法
//reduce 方法用于将数组中的每个元素(i)归约为单个值(在此处是一个对象 o,用于存储树形结构)。
const tree = arr.reduce((o, i) => {
//o[i.id] 尝试获取 o 中 i.id 对应的对象,如果不存在则创建一个空对象 {}。
//Object.assign 将 i 的属性复制到 o[i.id] 对象中,以确保每个节点在 o 中都有唯一的标识。
i = Object.assign(o[i.id] ??= {}, i);
//o[i.pid ?? ''] 尝试获取 o 中 i.pid 对应的对象,如果不存在则创建一个空对象 {}。
//o[i.pid ?? '']?.children 用来获取或初始化 o[i.pid] 的 children 数组,确保每个父节点都有一个子节点数组。
//push(i) 将当前节点 i 添加到其父节点的 children 数组中。
((o[i.pid ?? ''] ??= {}).children ??= []).push(i);
//reduce 方法返回的最终结果是一个对象 o,其中根节点的子节点数组存储在 o[''].children 中。
return o;
}, {})['']?.children
console.log(tree);
注意事项:
- 根节点识别: reduce 方法中,通过 o[‘’]?.children 来获取根节点的子节点数组,前提是根节点的 id 或 pid 被设为 ‘’。
- 空值合并运算符(??=): 这是 ECMAScript 2022(ES2022)中的新特性,用于更简洁地处理可能为 null 或 undefined 的情况。
- 在JavaScript中,?? 和 ??= 是合并运算符和其赋值形式的表达式。
??
是一个逻辑运算符,用于判断左侧操作数是否为null
或undefined
。如果是,则返回
右侧操作数,否则返回
左侧操作数的值。
示例:
let x = null ?? 'default'; // x 是 'default'
let y = 'value' ?? 'default'; // y 是 'value'
let z = 0 ?? 42; // z 是 0
??=
是 ?? 运算符的赋值形式,用于在左侧操作数为 null 或 undefined 时,将右侧操作数赋值
给左侧操作数。
示例:
let a = null;
a ??= 'default'; // 现在 a 的值是 'default'
let b = 'value';
b ??= 'default'; // b 的值不会改变,是 'value'
3、使用forEach方法
要根据每个元素的父子关系重新构建数据结构
假设有一个扁平化数组,每个元素包含 id 和 parent_id 字段来表示父子关系,树形数组的每个节点包含 id 和 children 字段。
function buildTree(flatArray) {
// 创建一个映射表,用于以id为键快速查找节点
let idMap = {};
flatArray.forEach(item => {
idMap[item.id] = { ...item, children: [] }; // 深拷贝每个item,并初始化children为空数组
});
let tree = [];
flatArray.forEach(item => {
if (item.parent_id !== null && idMap[item.parent_id]) {
// 如果不是根节点且父节点存在,则将当前节点添加到父节点的children数组中
idMap[item.parent_id].children.push(idMap[item.id]);
} else {
// 如果是根节点,直接添加到树的顶层
tree.push(idMap[item.id]);
}
});
//返回构建好的 tree 数组,其中包含了树的根节点和各层级的子节点。
return tree;
}
// 示例数组
let flatArray = [
{ id: 1, parent_id: null },
{ id: 2, parent_id: 1 },
{ id: 3, parent_id: 1 },
{ id: 4, parent_id: 2 },
{ id: 5, parent_id: 3 },
{ id: 6, parent_id: null },
{ id: 7, parent_id: 6 },
];
// 转换成树形数组
let treeArray = buildTree(flatArray);
console.log(treeArray);
4. 迭代方法
迭代方法通常使用循环和字典(或者 Map)来实现。它适用于处理层级不深、数据量较大的情况。
function buildTree(arr) {
const tree = {};
const map = {};
arr.forEach(node => {
map[node.id] = { ...node, children: [] };
});
Object.values(map).forEach(node => {
if (node.parent_id) {
map[node.parent_id].children.push(node);
} else {
tree[node.id] = node;
}
});
return Object.values(tree);
}
5.使用递归和映射(Map)
结合递归和 Map 对象,可以避免多次遍历和条件判断,提高效率。
function buildTree(arr) {
const map = new Map();
const tree = [];
arr.forEach(node => map.set(node.id, { ...node, children: [] }));
map.forEach(node => {
if (node.parent_id) {
const parent = map.get(node.parent_id);
if (parent) {
parent.children.push(node);
}
} else {
tree.push(node);
}
});
return tree;
}
6.使用 reduce 函数
利用数组的 reduce 方法,可以更紧凑地实现树的构建过程。
function buildTree(arr) {
const map = {};
const tree = [];
arr.reduce((acc, node) => {
map[node.id] = { ...node, children: [] };
if (node.parent_id === null) {
tree.push(map[node.id]);
} else {
const parent = map[node.parent_id];
if (parent) {
parent.children.push(map[node.id]);
}
}
return acc;
}, []);
return tree;
}
7.使用深度优先搜索(DFS)
深度优先搜索是一种递归的算法,通过遍历数组并递归构建树,可以有效地将扁平化数组转换为树形结构。
function buildTree(arr) {
const tree = {};
const dfs = (parentId) => {
return arr
.filter(node => node.parent_id === parentId)
.map(node => ({ ...node, children: dfs(node.id) }));
};
tree.children = dfs(null);
return tree.children;
}
8.使用广度优先搜索(BFS)
广度优先搜索可以通过迭代实现,使用队列来逐层构建树形结构,适合于需要按层级构建树的情况。
function buildTree(arr) {
const tree = []; // 初始化树形结构为一个空数组
const queue = [{ id: null, children: tree }];
while (queue.length > 0) {
const { id, children } = queue.shift();
arr.forEach((node) => {
if (node.parent_id === id) {
const newNode = { ...node, children: [] }; // 每个节点的 children 初始化为空数组
children.push(newNode); // 将新节点添加到父节点的 children 数组中
queue.push(newNode); // 将新节点加入队列,继续处理其子节点
}
});
}
return tree; // 返回整个树的数组形式
}
总结
提示:这里对文章进行总结:
递归 vs 迭代:
递归方法通常直观且易于理解,但在处理大量数据时可能会有性能上的挑战,可能面临堆栈溢出或性能问题。迭代方法和使用 Map 的方法通常可以提供更好的性能,特别是在数据量较大或层级较深的情况下,通常更适合处理大数据量和层级深的情况。选择合适的方法可以根据实际需求进行权衡和调整。
DFS vs BFS:
DFS适合递归实现,BFS适合迭代实现。选择取决于数据结构和需求,例如是否需要按层级构建树。