前言
日常业务前端中关于树的操作是需要得心应手的,如级联框、树型控件、表格树等。本篇文章整理常见的树操作,便于快速的CV。
数组转树形结构
常见于后端返回的JSON数据需要前端去转化为树型数据。
案例
[{
id: 1,
pid: 0,
name: '节点1'
},{
id: 2,
pid: 1,
name: '节点1-1'
},{
id: 3,
pid: 1,
name: '节点1-2'
},{
id: 4,
pid: 0,
name: '节点2'
},{
id: 5,
pid: 4,
name: '节点2-1'
}]
实现
- 非递归实现
function arrayToTree(data) {
let res = []
if(!Array.isArray(data)) return res
let treeMap = {}
data.forEach(item => treeMap[item.id] = item)
data.forEach(item => {
let parent = treeMap[item.pid]
if(parent) {
parent.children = !parent.children ? [] : parent.children
parent.children.push(item)
} else {
res.push(item)
}
})
return res
}
- 递归实现
function arrayToTree(data, pid) {
let res = []
if(!Array.isArray(data)) return res
data.forEach(item => {
if(item.pid === pid) {
let children = arrayToTree(data, item.id)
children.length && (item.children = children)
res.push(item)
}
})
return res
}
树形结构转数组
案例
[
{
id: 1,
pid: 0,
name: '节点1',
children: [{
id: 2,
pid: 1,
name: '节点1-1'
},{
id: 3,
pid: 1,
name: '节点1-2'
}]
},
{
id: 4,
pid: 0,
name: '节点2',
children: [{
id: 5,
pid: 4,
name: '节点2-1'
}]
}
]
实现
- 广度遍历
function treeToArr(data) {
let res = []
while(data.length) {
let length = data.length
for(let i = 0; i < length; i++) {
let item = data.shift()
if(item.children && item.children.length) {
for(let j = 0; j < item.children.length; j++) {
data.push(item.children[j])
}
}
res.push({
id: item.id,
pid: item.pid,
name: item.name
})
}
}
return res
}
- 深度遍历
function TreeToArr(data) {
let res = []
let stack = []
for(let i = data.length - 1; i >= 0; i--) {
stack.push(data[i])
}
while(stack.length) {
let item = stack.pop()
res.push({
id: item.id,
pid: item.pid,
name: item.name
})
let children = item.children
if(children && children.length) {
for(let i = children.length - 1; i >= 0; i--) {
stack.push(children[i])
}
}
}
return res
}
树的高度
案例
const data = [{
id: 1,
children: [{
id: 2,
children: [{
id: 3,
children: []
}]
},
{
id: 4,
children: []
}]
}]
实现
- 广度遍历
function getTreeHeight(data) {
let queue = []
let height = 0
if(!Array.isArray(data)) return height
if(Array.isArray(data) && data.length > 0) queue[0] = data[0]
while(queue.length) {
let length = queue.length
height++
for(let i = 0; i < length; i++) {
let item = queue.shift()
if(item && item.children) {
for(let j = 0; j < item.children.length; j++) {
item.children[j] && queue.push(item.children[j])
}
}
}
}
return height
}
- 深度遍历 深度遍历获取所有路径,再遍历路径数组获取最长路径即最大高度。
function getTreeHeight(data) {
const paths = []
let height = 0
function dfs(data, prefix) {
for(let i = 0; i < data.length; i++) {
if(data[i].children && data[i].children.length) {
const path = prefix + data[i].id + ','
dfs(data[i].children, path)
} else {
paths.push(prefix + data[i].id)
}
}
}
dfs(data, "")
for(let i = 0; i < paths.length; i++) {
let pathArr = paths[i].split(',')
height = Math.max(height, pathArr.length)
}
return height
}
查找节点在树中的路径
案例
[
{
id: 1,
pid: 0,
name: '节点1',
children: [{
id: 2,
pid: 1,
name: '节点1-1'
},{
id: 3,
pid: 1,
name: '节点1-2'
}]
},
{
id: 4,
pid: 0,
name: '节点2',
children: [{
id: 5,
pid: 4,
name: '节点2-1'
}]
}
]
实现
function getNodePath(data, id) {
if(!Array.isArray(data)) return []
let path = []
function findTreeNode(tree, id) {
for(let i = 0; i < tree.length; i++) {
path.push(tree[i].id)
if(tree[i].id === id) return path
let children = tree[i].children
if(children && children.length) {
let findChildrenPath = findTreeNode(children, id)
if(findChildrenPath && findChildrenPath.length) return findChildrenPath
}
path.pop()
}
return []
}
return findTreeNode(data, id)
}
查找/编辑/删除节点
查找、编辑、删除操作都是基于遍历寻找对应 id,再去执行相关操作,这里以查找为例。
案例
[
{
id: 1,
pid: 0,
name: '节点1',
children: [{
id: 2,
pid: 1,
name: '节点1-1'
},{
id: 3,
pid: 1,
name: '节点1-2'
}]
},
{
id: 4,
pid: 0,
name: '节点2',
children: [{
id: 5,
pid: 4,
name: '节点2-1'
}]
}
]
实现
深度优先遍历
function findTreeNode(tree, id) {
for(let i = 0; i < tree.length; i++) {
let node = tree[i]
if(node.id === id) {
return node
}
let children = node.children
if(children && children.length) {
let childrenNode = findTreeNode(children, id)
if(childrenNode) return childrenNode
}
}
return
}
获取所有的叶子节点
案例
[
{
id: 1,
pid: 0,
name: '节点1',
children: [{
id: 2,
pid: 1,
name: '节点1-1'
},{
id: 3,
pid: 1,
name: '节点1-2'
}]
},
{
id: 4,
pid: 0,
name: '节点2',
children: [{
id: 5,
pid: 4,
name: '节点2-1'
}]
}
]
实现
function getLeafNode(data) {
let res = []
function dfs(tree) {
for(let i = 0; i < tree.length; i++) {
let children = tree[i].children
if(!children || children.length === 0) {
res.push({
id: tree[i].id,
pid: tree[i].pid,
name: tree[i].name
})
} else {
dfs(children)
}
}
}
dfs(data)
return res
}