JS中的树形数据结构处理

6,545 阅读5分钟

前言

树形数据的遍历方式本质上是数据结构二叉树遍历的主要思想。所以以下遍历方式主要是广度优先遍历(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'))