想起博主前几个月的面试,不免感叹,还真是愿意考东西~,话不多说,咱们进入正题!
一、数组转为树结构
1、递归(大法好)
const list = [{
"id": 18,
"parentId": 16
}, {
"id": 19,
"parentId": 0
},
{
"id": 16,
"parentId": 0
},
{
"id": 20,
"parentId": 19
},
{
"id": 17,
"parentId": 16
},
{
"id": 22,
"parentId": 17
},
{
"id": 21,
"parentId": 18
},
{
"id": 23,
"parentId": 22
},
{
"id": 24,
"parentId": 23
}
];
function arrayToTree(arr, pid) {
let res = [];
arr.forEach((item) => {
// 等于0即为根节点
if (item.parentId === pid) {
// 一层层遍历 直到不是 根节点为止
item.children = arrayToTree(arr, item.id);
res.push(item);
}
});
return res;
}
console.log('arrayToTree', arrayToTree(list, 0));
- 虽然递归的代码简洁且容易理解,但递归的时间复杂度有点高;
2、数组转为树结构(双层循环)
// id相匹配的元素是父子级关系
const list = [{
"id": 18,
"parentId": 16,
}, {
"id": 19,
"parentId": 0,
},
{
"id": 16,
"parentId": 0,
},
{
"id": 20,
"parentId": 19
},
{
"id": 17,
"parentId": 16,
},
{
"id": 22,
"parentId": 17,
},
{
"id": 21,
"parentId": 18,
},
{
"id": 23,
"parentId": 22,
},
{
"id": 24,
"parentId": 23,
},
];
// 初级解法(容易想到)下面会单独拆解该方法 ~ qiudemadai
function arrayToTree(array) {
// 创建一个映射对象
const obj = {};
array.forEach(item => {
// 为数组初始化一个children = []
item.children = []
obj[item.id] = item;
});
// 创建一个空数组,用于存放树节点
const tree = [];
array.forEach(item => {
// 获取父节点
const parent = obj[item.parentId];
// 如果有父节点
if (parent) {
// 将当前元素推入父节点的children数组
parent.children.push(item);
} else {
// 如果没有父节点,就说明是根节点,推入tree数组
tree.push(item);
}
});
// 返回tree数组
return tree;
}
// 调用函数,打印结果
console.log(arrayToTree(list));
解析-(双层循环))
const obj = {};
// 第一等循环的作用是 对obj对象赋值, 将obj对象改造成包含 id,parentId,children的数据格式,
// 方便后面在第二层循环取值使用
array.forEach(item => {
// 为数组初始化一个children = []
item.children = []
// 用数组中的id 为对象的key,对象的Value就是数组中的每一项
obj[item.id] = item;
});
obj[item.id] = item
的含义是:将obj用数组中的id 为对象的key,obj 的 value 就是 list 数组中的每一项,再加上前面在循环中的item.children = []
,obj的大概数据格式如图:key: item.id
;value: 三项组成id,parentId,children
- 然后进入第二层循环,第二层循环中首先创建了一个数组 tree ,用来存储树节点,
const parent = obj[item.parentId]
这个行代码需要认真理解,这行代码的意思是用 list 数组中每一项的parentId,在 obj 对象中进行匹配,这就是我上面说的用处,注意这里面有两项({ "id": 19, "parentId": 0, }, { "id": 16, "parentId": 0, },)
的parentId是0,所以我们可以根据 parentId 是否为0,来区分是否为根节点,如果是根节点,则直接将节点push,否则将parent.children放进去。
// 创建一个空数组,用于存放树节点
const tree = [];
array.forEach(item => {
// 获取父节点
const parent = obj[item.parentId];
console.log('papaa----parent', parent);
// 如果有父节点
if (parent) {
// 将当前元素推入父节点的children数组
parent.children.push(item);
} else {
// 如果没有父节点,就说明是根节点,推入tree数组
tree.push(item);
}
});
const parent = obj[item.parentId]
这行代码执行完成后,大致样子如图,我做了一个匹配,帮助大家理解。
- 最后可以看见这个树结构的匹配性是很高的,可以多层树结构嵌套~如图
3、数组转为树结构(单层循环)
- 回想刚刚的代码,利用了双层for循环,比较容易理解,但是仔细想一下 是不是第一层for(为obj放id,parentId,children)这一步可以也放到一层循环中,改造如下:
const list = [{
"id": 18,
"parentId": 16
}, {
"id": 19,
"parentId": 0
},
{
"id": 16,
"parentId": 0
},
{
"id": 20,
"parentId": 19
},
{
"id": 17,
"parentId": 16
},
{
"id": 22,
"parentId": 17
},
{
"id": 21,
"parentId": 18
},
{
"id": 23,
"parentId": 22
},
{
"id": 24,
"parentId": 23
}
];
function arrayToTree(list) {
const result = []; // 存放结果
const current = {};
for (const item of list) {
const id = item.id;
const parentId = item.parentId;
// 没有这个属性 就创建一个children 初始化为空
if (!current[id]) {
current[id] = {
children: []
};
}
current[id] = Object.assign(item, current[id]);
console.log('current', current);
const treeItem = current[id];
if (!parentId) {
// 根节点
result.push(treeItem);
} else {
// 如果发现是children 并且没有找到对应父级的 parentId, 就初始化父级的children
if (!current[parentId]) {
current[parentId] = {
children: []
};
}
current[parentId].children.push(treeItem);
}
}
return result;
}
console.log('arrayToTree', arrayToTree(list));
解析-(单层循环))
- 同样我打印了对象current的值,结果如下,看起来效果没差,但却减少了一层循环。
- 输出结果如下,也是一点没差,看来单层循环已经足够!这里补充下为什么不直接来单层循环的写法,因为我个人觉得双层循环是我很容易想到的,明白了双层循环的含义,才能更好的理解单层循环~
二、树结构转为数组
1、递归大法好
const tree = [
{
"id": 16,
"parentId": 0,
"children": [
{
"id": 18,
"parentId": 16,
"children": []
},
{
"id": 17,
"parentId": 16,
"children": []
}
]
},
{
"id": 19,
"parentId": 0,
"children": [
{
"id": 20,
"parentId": 19,
"children": [
{
"id": 21,
"parentId": 20,
"children": [
{
"id": 22,
"parentId": 21,
"children": [
{
"id": 23,
"parentId": 22,
"children": [
{
"id": 24,
"parentId": 23,
"children": []
}
]
}
]
}
]
}
]
}
]
}
];
function treeToArray(tree) {
let res = []
for (const item of tree) {
const { children, ...i } = item
/// i是对 item的重新命名
if (children && children.length) {
res = res.concat(treeToArray(children))
}
res.push(i)
}
return res
}
2、队列
const tree = [
{
"id": 16,
"parentId": 0,
"children": [
{
"id": 18,
"parentId": 16,
"children": []
},
{
"id": 17,
"parentId": 16,
"children": []
}
]
},
{
"id": 19,
"parentId": 0,
"children": [
{
"id": 20,
"parentId": 19,
"children": [
{
"id": 21,
"parentId": 20,
"children": [
{
"id": 22,
"parentId": 21,
"children": [
{
"id": 23,
"parentId": 22,
"children": [
{
"id": 24,
"parentId": 23,
"children": []
}
]
}
]
}
]
}
]
}
]
}
];
function flattenTree(tree) {
// 创建空数组 存放扁平化结果
const result = [];
// 将根节点或整棵树的节点放入queue中
const queue = [...tree];
while (queue.length) {
// 从队列queue中取出队首元素
const node = queue.shift();
result.push({
id: node.id,
parentId: node.parentId
});
if (node.children) {
// ...node.children 就是相当于取出数组中的每一项放到queue中
queue.push(...node.children);
}
}
return result;
}
const flattenedTree = flattenTree(tree);
console.log(flattenedTree);