给定包含节点关系的一维数组数据,如下:
[
{
"name": "四川省",
"id": 1,
"parentId": null
},
{
"name": "成都市",
"id": 2,
"parentId": 1
},
{
"name": "绵阳市",
"id": 3,
"parentId": 1
},
{
"name": "自贡市",
"id": 4,
"parentId": 1
},
{
"name": "泸州市",
"id": 5,
"parentId": 1
},
{
"name": "达州市",
"id": 6,
"parentId": 1
},
{
"name": "锦江区",
"id": 7,
"parentId": 2
},
{
"name": "青羊区",
"id": 8,
"parentId": 2
},
{
"name": "金牛区",
"id": 9,
"parentId": 2
},
{
"name": "武侯区",
"id": 10,
"parentId": 2
},
{
"name": "成华区",
"id": 11,
"parentId": 2
},
{
"name": "香木林路",
"id": 12,
"parentId": 11
},
{
"name": "建工路",
"id": 13,
"parentId": 11
},
{
"name": "五福桥东路",
"id": 14,
"parentId": 9
},
{
"name": "顺城大街",
"id": 15,
"parentId": 7
},
{
"name": "浣花南路",
"id": 16,
"parentId": 8
},
{
"name": "四川博物院",
"id": 17,
"parentId": 16
},
{
"name": "草堂小学",
"id": 18,
"parentId": 16
},
{
"name": "涪城区",
"id": 19,
"parentId": 3
},
{
"name": "游仙区",
"id": 20,
"parentId": 3
},
{
"name": "安州区",
"id": 21,
"parentId": 3
}
]
构建树结构,并标记叶子结点
function buildTree(rawData) {
// 可选。深拷贝不影响原始数据
const data = JSON.parse(JSON.stringify(rawData));
// 定义结果
const result = [];
// 初始化dataMap,将一维数组构造成以id为键的map对象
const dataMap = {};
data.forEach(item => {
dataMap[item.id] = item;
// 默认为叶子节点
item.isLeaf = true;
// 默认不存在子节点
item.children = [];
});
// 遍历数组,构建树结构。注意,这里在循环中直接修改了data中的数据。
data.forEach(item => {
// 获取当前元素的父节点
const parent = dataMap[item.parentId];
if (parent) {
// 父元素存在,标记父元素为非叶子节点。
parent.isLeaf = false;
// 将元素作为子节点插入父元素中
parent.children.push(item);
} else {
// 父元素不存在,为根节点。判断是否存在子元素,标记叶子节点状态。
// 这里标记叶子节点的时候,可能子元素还没插入到父元素中,结果标记为了叶子节点,但是上面的if判断成立,子元素会再标记一次这个根节点为非叶子节点。
item.isLeaf = item.children.length === 0;
// 父元素不存在,为根节点, 直接插入到result中。
result.push(item);
}
});
return result;
}
其他关注点
上述程序修改了原始数据,如果不想影响原始输入数据,可以通过loadsh的方法const data = lodash.cloneDeep(rawData)
或者const data = JSON.parse(JSON.stringify(rawData))
进行一次深拷贝。
代码的时间复杂度是2n,一个初始化数据的for循环,一个构建树结构的for循环。
如果原始数据已经标记了叶子节点的信息,就可以把代码中标记叶子节点的相关逻辑删掉了。
最后的输出结果
[
{
"name": "四川省",
"id": 1,
"parentId": null,
"isLeaf": false,
"children": [
{
"name": "成都市",
"id": 2,
"parentId": 1,
"isLeaf": false,
"children": [
{
"name": "锦江区",
"id": 7,
"parentId": 2,
"isLeaf": false,
"children": [
{
"name": "顺城大街",
"id": 15,
"parentId": 7,
"isLeaf": true,
"children": [
]
}
]
},
{
"name": "青羊区",
"id": 8,
"parentId": 2,
"isLeaf": false,
"children": [
{
"name": "浣花南路",
"id": 16,
"parentId": 8,
"isLeaf": false,
"children": [
{
"name": "四川博物院",
"id": 17,
"parentId": 16,
"isLeaf": true,
"children": [
]
},
{
"name": "草堂小学",
"id": 18,
"parentId": 16,
"isLeaf": true,
"children": [
]
}
]
}
]
},
{
"name": "金牛区",
"id": 9,
"parentId": 2,
"isLeaf": false,
"children": [
{
"name": "五福桥东路",
"id": 14,
"parentId": 9,
"isLeaf": true,
"children": [
]
}
]
},
{
"name": "武侯区",
"id": 10,
"parentId": 2,
"isLeaf": true,
"children": [
]
},
{
"name": "成华区",
"id": 11,
"parentId": 2,
"isLeaf": false,
"children": [
{
"name": "香木林路",
"id": 12,
"parentId": 11,
"isLeaf": true,
"children": [
]
},
{
"name": "建工路",
"id": 13,
"parentId": 11,
"isLeaf": true,
"children": [
]
}
]
}
]
},
{
"name": "绵阳市",
"id": 3,
"parentId": 1,
"isLeaf": false,
"children": [
{
"name": "涪城区",
"id": 19,
"parentId": 3,
"isLeaf": true,
"children": [
]
},
{
"name": "游仙区",
"id": 20,
"parentId": 3,
"isLeaf": true,
"children": [
]
},
{
"name": "安州区",
"id": 21,
"parentId": 3,
"isLeaf": true,
"children": [
]
}
]
},
{
"name": "自贡市",
"id": 4,
"parentId": 1,
"isLeaf": true,
"children": [
]
},
{
"name": "泸州市",
"id": 5,
"parentId": 1,
"isLeaf": true,
"children": [
]
},
{
"name": "达州市",
"id": 6,
"parentId": 1,
"isLeaf": true,
"children": [
]
}
]
}
]