如何快速查找树节点数据

3,081 阅读3分钟

树结构在编程中是非常常见的数据结构,常见的操作为根据ID找节点等行为,并且修改数据的行为。

如下是一个非常的树结构数据,上下级关系,由 id 和 parentId 进行维护和关联。

[
  { "id": 10000, "parentId": null, "text": "item 1" },
  { "id": 20000, "parentId": null, "text": "item 2" },
  { "id": 11000, "parentId": 10000, "text": "item 1-1" },
]

如果我们想查找某一个节点的数据,通常有如下场景:

  • 由上往下找
    1. 知道父级,找子级
    2. 知道爷爷级,找子孙级
  • 由下往上找
    1. 知道子级,找父级
    2. 知道子级,找爷爷级
  • 匹配找
    1. 根据ID匹配
    2. 根据关键字匹配
    3. 根据某个字段匹配

以上场景中,很多新手通常想到的就是递归查找,这种效率是非常慢的,其实我们可以换个思路,结合我们学到的 对象引用,以数据打平的方式,就可以实现快速查找任何节点的功能。

打平数据结构

如果一开始就是行数据,转为树结构数据,那我们可以这样做

import { toTree } from '@zhengxs/js.tree'

// 原始数据
const data = [
  { "id": 10000, "parentId": null, "text": "item 1" },
  { "id": 20000, "parentId": null, "text": "item 2" },
  { "id": 11000, "parentId": 10000, "text": "item 1-1" },
]

// 所有节点的数据
// 用于后续数据的快速查找
const nodeMap = {}

// 行转树
const result = toTree(data, {
  transform(node) {
  
    // 保存对象的引用
    nodeMap[node.id] = node 
    
    return node
  },
})

如果开始的数据就是树结构的,那可以在数据获取的最开始位置进行一次递归操作。

import { each } from '@zhengxs/js.tree'

// 所有节点的数据
// 用于后续数据的快速查找
const nodeMap = {}

each(data, node => {
    // 保存对象的引用
    nodeMap[node.id] = node 
})

现在你手上就有2份数据,一份是 树结构 数据,一份是 树节点映射 数据,因为对象的引用关系,所以你修改 树节点映射 的数据,也会修改到 树结构 数据。

树结构数据

[
  {
    "id": 10000,
    "parentId": null,
    "text": "item 1",
    "children": [
      {
        "id": 11000,
        "parentId": 10000,
        "text": "item 1-1",
        "children": []
      }
    ]
  },
  {
    "id": 20000,
    "parentId": null,
    "text": "item 2",
    "children": []
  }
]

树节点数据

// 因为是对象,所以值依然保持着引用的关系
{
  "10000": Object {id: 10000, parentId: null, text: "item 1",},
  "11000": Object {id: 11000, parentId: 10000, text: "item 1-1",},
  "20000": Object {id: 20000, parentId: null, text: "item 2",}
}

在线尝试

快速查找

知道某一级ID,如何查找

因为我们已经把数据打平,所以我们现在可以根据对象直接获取。

// 假设找 11000 的节点
const id = "11000"

// 可以通过 nodeMap 直接获取
const currentNode = nodeMap[id]

console.log(currentNode)
// Object {id: 11000, parentId: 10000, text: "item 1-1", children: []}

在线尝试

知道子ID,如何查找父级或祖先级

找直接父级

// 假设找 11000 的节点的父级或祖先级
const id = "11000"

// 可以通过 nodeMap 直接获取
const currentNode = nodeMap[id]

// 直接父亲
const parentNode = nodeMap[currentNode['parentId']]

console.log(parentNode)
// Object {id: 10000, parentId: null, text: "item 1", …}

找所有上级数据

// 假设找 11000 的节点的父级或祖先级
const id = "11000"

// 可以通过 nodeMap 直接获取
const currentNode = nodeMap[id]

// 所有上级数据
const parentsNodes = []

let _parent
let _parentId = currentNode['parentId']

// 死循环
while(true) {
   // 获取父级数据
   _parent = nodeMap[_parentId]
   
   // 如果上级不存在就停止循环
   if (_parent == null) break
   
   // 保存上级数据
   parentsNodes.push(_parent)
   
   // 指向当前的上级
   _parentId = _parent['parentId']
}

console.log(parentsNodes)
// [
//   Object {id: 10000, parentId: null, text: "item 1", …}
// ]

在线尝试

知道内容如何匹配

根据文字匹配

const query = "item 1"

const result2 = []

for (const key in nodeMap) {
    const node = nodeMap[key]
    
    // 如果是精确匹配用 ===
    // 如果仅查找一个,找到 break 就可以了
    if (node.text.indexOf(query) > -1) {
      result2.push(node)
    }
}

console.log(result2)
// [
//   Object {id: 10000, parentId: null, text: "item 1", …}
//   Object {id: 11000, parentId: 10000, text: "item 1-1", children: []}
// ]

在线尝试