前言
最近在这三月份,陆陆续续接了了几个笔试题。其中有一个常考的题目。就是后台返回一个扁平的数据结构,转成树。
我们看下题目:打平的数据内容如下:
js
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
输出结果
js
[
{
id: 1,
name: '部门1',
children: [
{
id: 2,
name: '部门2',
children: []
},
{
id: 3,
name: '部门3',
children: [
{
id: 4,
name: '部门4',
children: [
{
id: 5,
name: '部门5',
children: []
}
]
}
]
}
]
}
]
解决思路
递归
主要思路是提供一个arrayToTree
的方法,该方法递归
去查找子集。
js
/**
* 递归查找
*/
function arrayToTree(array,pid){
let result = []
array.forEach(item=>{
if(item.pid==pid){
item.children = arrayToTree(array,item.id)
result.push(item)
}
})
return result
}
从上面的代码我们分析,该实现的时间复杂度为O(2^n)
你。
为什么时间复杂度是O(2^n)
呢
首先,让我们来看函数中的主要操作:
- 遍历数组:代码中使用了
forEach
方法对数组进行了一次遍历。这一操作的时间复杂度为 O(n),其中 n 是数组的长度。 - 递归调用:在遍历数组的过程中,对于每个节点,都会进行递归调用
arrayToTree
函数来构建其子树。在最坏情况下,每次递归调用都需要遍历一次整个数组来寻找与当前节点的父节点 ID 相同的节点。
因此,递归调用的总时间复杂度为 O(n^2),其中 n 是数组的长度。
不用递归,如何优化
主要思路是先把数据转成Map
去存储,之后遍历的同时借助对象的引用
,直接从Map
找对应的数据做存储。这样借助额外的空间,降低了时间复杂度
js
function arrayToTree(items) {
const result = []; // 存放结果
const itemMap = {};
// 先转成map存储
for (const item of items) {
itemMap[item.id] = { ...item, children: [] };
}
for (const item of items) {
const id = item.id;
const pid = item.pid;
const treeItem = itemMap[id];
if (pid === 0) {
result.push(treeItem);
} else {
if (!itemMap[pid]) {
itemMap[pid] = {
children: [],
};
}
itemMap[pid].children.push(treeItem);
}
}
return result;
}
复杂度为多少
-
创建哈希表:在第一个循环中,我们遍历了一次输入的
items
数组,并将每个节点都添加到了哈希表itemMap
中。这个操作的时间复杂度是 O(n),其中 n 是数组的长度。 -
构建树:在第二个循环中,我们再次遍历了一次
items
数组,对每个节点进行处理。对于每个节点,我们都需要访问其父节点,并将当前节点添加到父节点的children
数组中。在访问和更新节点时,由于使用了哈希表,查找和访问节点的时间复杂度是 O(1),因此对每个节点的操作的时间复杂度也是 O(1)。因此,整个构建树的过程的时间复杂度是 O(n),其中 n 是数组的长度。
综上所述,整个算法的时间复杂度是 O(n),其中 n 是输入数组的长度。这是因为我们只需要遍历两次输入数组,并且在每次操作中都能以常数时间复杂度在哈希表中查找到对应的节点。。
结尾
如果你有着比以上更好的实现方法,欢迎在评论区留下你的答案,大家一起学习。