前言
树形数据的遍历方式本质上是数据结构二叉树
遍历的主要思想。所以以下遍历方式主要是广度优先遍历(BFS)
和深度优先遍历(DFS)
。
遍历方式:
- 广度优先遍历(广度优先搜索在二叉树上的应用:即层序遍历。)
即逐层地,从左到右访问所有节点。 - 深度优先遍历
- 前序遍历 - 遍历顺序:中左右: 1. 递归法; 2. 迭代法(压栈顺序:右左中)
- 中序遍历 - 遍历顺序:左中右: 1. 递归法; 2. 迭代法(压栈顺序:左中右)
- 后序遍历 - 遍历顺序:左右中: 1. 递归法; 2. 迭代法(压栈顺序:左右中)
遍历思想:迭代法 - 栈; 层序遍历 - 队列
相关处理方法
// 用于测试的树数据
const treeData = [
{
id: '1',
name: '测试1',
pId: null,
type: 'group',
children: [
{
id: '11',
name: '测试11',
pId: '1',
children: [
{
id: '111',
name: '测试111',
pId: '11',
type: 'group',
children: [
{
id: '1111',
name: '测试1111',
pId: '111',
},
{
id: '1112',
name: '测试1112',
pId: '111',
}
]
},
{
id: '112',
name: '测试112',
pId: '11',
children: [
{
id: '1121',
name: '测试1121',
pId: '112',
}
]
},
{
id: '113',
name: '测试113',
pId: '11',
},
]
},
{
id: '12',
name: '测试12',
pId: '1',
type: 'group',
children: [
{
id: '121',
name: '测试121',
pId: '12',
}
]
},
{
id: '13',
name: '测试13',
pId: '1'
},
{
id: '14',
name: '测试14',
pId: '1'
}
]
},
{
id: '2',
name: '测试2',
pId: null,
children: [
{
id: '21',
name: '测试21',
pId: '2',
children: [
{
id: '211',
name: '测试211',
pId: '21',
},
{
id: '212',
name: '测试212',
pId: '21',
}
]
},
{
id: '22',
name: '测试22',
pId: '2'
},
]
}
]
1. 查找当前节点
DFS
const getNodeDFS = (data, id) => {
for (const node of data) {
if (node.id === id) {
return node
}
if (node.children?.length) {
const curNode = getNodeDFS(node.children, id)
if (curNode) return curNode
}
}
return null
}
console.log('1. 查找当前节点 DFS 111', getNodeDFS(treeData, '111'))
BFS
const getNodeBFS = (data, id) => {
const queue = [...data]
while (queue.length) {
const node = queue.shift()
if (node.id === id) {
return node
}
if (node.children?.length) {
queue.push(...node.children)
}
}
return null
}
console.log('1. 查找当前节点 BFS 111', getNodeBFS(treeData, '111'))
2. 获取当前节点的所有子节点的id
DFS
const getAllChildrenIdDFS = (data, id) => {
const result = []
let collecting = false
const traverse = (nodes, idArr, idChild = false) => {
for (const node of nodes) {
// 如果已找到目标节点,收集当前节点ID
if (collecting) {
result.push(node.id)
}
// 检查当前节点是否是目标节点
if (node.id === id) {
collecting = true
}
// 递归遍历子节点
if (node.children?.length) {
traverse(node.children, id)
}
// 如果当前节点是目标节点的父节点,重置found标志
if (node.id === id) {
collecting = false
}
}
}
traverse(data, result)
return result
}
console.log('2. 获取当前节点的所有子节点的id DFS 11', getAllChildrenIdDFS(treeData, '11'))
BFS
const getAllChildrenIdBFS = (data, id) => {
const queue = JSON.parse(JSON.stringify(data))
while (queue.length) {
const node = queue.shift()
// 找到目标节点后,开始收集其子节点ID
if (node.id === id) {
const result = []
const subQueue = node.children ? [...node.children] : []
while (subQueue.length) {
const child = subQueue.shift()
result.push(child.id)
if (child.children) subQueue.push(...child.children)
}
return result
}
// 将当前节点的子节点加入队列继续搜索
if (node.children) queue.push(...node.children)
}
return []
}
console.log('2. 获取当前节点的所有子节点的id BFS 11', getAllChildrenIdBFS(treeData, '11'))
3. 判断树中有无当前节点id
DFS
const hasIdDFS = (data, id) => {
for (const node of data) {
if (node.id === id) return true
if (node.children && hasIdDFS(node.children, id)) return true
}
return false
}
console.log('3. 判断树中有无当前节点id DFS 1111', hasIdDFS(treeData, '1111'))
BFS
const hasIdBFS = (data, id) => {
const queue = [...data]
while (queue.length) {
const node = queue.shift()
if (node.id === id) return true
if (node.children) queue.push(...node.children)
}
return false
}
console.log('3. 判断树中有无当前节点id BFS 1111', hasIdBFS(treeData, '1111'))
4. 树形数据扁平化
DFS
const treeToFlatDFS = (data) => {
const result = []
for (const node of data) {
const obj = { ...node }
delete obj.children
result.push(obj)
if (node.children) result.push(...treeToFlatDFS(node.children))
}
return result
}
console.log('4. 树形数据扁平化 DFS', treeToFlatDFS(treeData))
BFS
const treeToFlatBFS = (data) => {
const queue = [...data]
const result = []
while (queue.length) {
const node = queue.shift()
const obj = { ...node }
delete obj.children
result.push(obj)
if (node.children) queue.push(...node.children)
}
return result
}
const flatData = treeToFlatBFS(treeData)
console.log('4. 树形数据扁平化 BFS', flatData)
5. 扁平化数据转树形结构
DFS
const flatToTreeDFS = (data, pId = null) => {
return data
.filter(item => item.pId === pId)
.map(item => ({
...item,
children: flatToTreeDFS(data, item.id)
}))
}
console.log('5. 扁平化数据转树形数据结构 DFS', flatToTreeDFS(flatData))
BFS
const flatToTreeBFS = (data) => {
const map = {}
const root = []
// 创建映射表
data.forEach(item => {
map[item.id] = {
...item,
children: []
}
})
// 构建树形结构
data.forEach(item => {
const node = map[item.id]
if (!item.pId) {
root.push(node)
} else if (map[item.pId]) {
map[item.pId].children.push(node)
}
})
return root
}
console.log('5. 扁平化数据转树形数据结构 BFS', flatToTreeBFS(flatData))
6. 查找当前节点的父节点
DFS
const getPNodeDFS = (data, id) => {
for (const node of data) {
if (node.children?.length) {
if (node.children.some(child => child.id === id)) {
return node
} else {
const pNode = getPNodeDFS(node.children, id)
if (pNode) return pNode
}
}
}
return null
}
console.log('6、查找当前节点的父节点 DFS 1111: ', getPNodeDFS(treeData, '1111'))
BFS
const getPNodeBFS = (data, id) => {
const queue = [...data]
while (queue.length) {
const node = queue.shift()
if (node.children?.length) {
if (node.children.some(child => child.id === id)) {
return node
}
queue.push(...node.children)
}
}
return null
}
console.log('6、查找当前节点的父节点 BFS 1111: ', getPNodeBFS(treeData, '1111'))
7. 查找当前节点的所有祖先节点(例:当前节点:1111,所有祖先节点为:1,11,111)
DFS
const getAllPNodeDFS = (data, id) => {
const traverse = (nodes, path) => {
for (const node of nodes) {
const currentPath = [...path, node]
if (node.id === id) {
return path
}
if (node.children?.length) {
const found = traverse(node.children, currentPath)
if (found) return found
}
}
}
const result = traverse(data, [])
return result || []
}
console.log('7、查找当前节点的所有祖先节点 DFS 1111:', getAllPNodeDFS(treeData, '1111'))
BFS
const getAllPNodeBFS = (data, id) => {
const queue = data.map(node => ({ node, path: [] }))
while (queue.length) {
const { node, path } = queue.shift()
if (node.id === id) {
return path
}
if (node.children?.length) {
for (const child of node.children) {
queue.push({ node: child, path: [...path, node] })
}
}
}
return []
}
console.log('7、查找当前节点的所有祖先节点 BFS 1111:', getAllPNodeBFS(treeData, '1111'))
8、树的最大深度
DFS
const getMaxDepthDFS = (data) => {
let maxDepth = 0
for (const node of data) {
let depth = 1
if (node.children) depth += getMaxDepthDFS(node.children)
maxDepth = Math.max(maxDepth, depth)
}
return maxDepth
}
console.log('8、树的最大深度 DFS', getMaxDepthDFS(treeData))
BFS
const getMaxDepthBFS = (data) => {
let depth = 0
const queue = [...data]
while (queue.length) {
depth++
const levelLen = queue.length
for (let i = 0; i < levelLen; i++) {
const node = queue.shift()
node.children && queue.push(...node.children)
}
}
return depth
}
console.log('8、树的最大深度 BFS', getMaxDepthBFS(treeData))
9. 筛选符合条件的数据,保留原有树形结构
const filterTreeWithStructureDFS = (data, condition) => {
const traverse = (node) => {
// 复制节点但不包含子节点
const newNode = { ...node }
delete newNode.children
if (node.children) {
const filteredChildren = node.children
.map(traverse)
.filter(child => child)
if (filteredChildren.length) {
newNode.children = filteredChildren
}
}
// 保留节点条件:自身符合条件或有符合条件的子节点
if (condition(node) || newNode.children?.length) {
return newNode
}
}
return data.map(traverse).filter(node => node)
}
console.log('9. 筛选符合条件的数据,保留原有树形结构', filterTreeWithStructureDFS(treeData, (node) => node.type === 'group'))