面试被问到扁平数据结构转Tree这个问题怎么办?

本文讲述了在面试中遇到扁平数据结构转换为树形结构问题的应对策略,包括基本思路、JavaScript代码实现以及性能优化的方法,如减少循环次数、使用哈希表和排序。
摘要由CSDN通过智能技术生成

面试被问到扁平数据结构转Tree这个问题怎么办 ?

哎呀,被问到扁平数据结构转Tree这个问题,这是面试官刁难你的高频问题之一啊。回答不好,面试官就会觉得你只是会递归的小学生,完全不值得高薪聘用啊!

不过别慌,跟着我念一遍这篇文章,你就能迅速懂得如何回答这个问题了。

常规方法

首先,我们要明确一个概念:扁平数据结构就是所有数据都在同一层级下,没有任何层级结构;而树形数据结构则是一种层级结构,具有一个根节点和多个子节点。所以,我们转换扁平数据结构为树形数据结构的关键在于识别每个节点的父子关系。

接下来,你可以对面试官大喊一声:“听好了,咳咳,我要开始讲思路了!” 那么,我们可以通过以下步骤将扁平数据结构转换为树形数据结构:

  1. 创建一个空的根节点
  2. 遍历所有节点,并根据其parentId找到对应的父节点
  3. 将当前节点添加为父节点的子节点
  4. 如果当前节点没有父节点,它将成为新的根节点

是不是非常简单啊!下面,我来展示一个JavaScript代码实现,假设我们有以下扁平数据结构:

[
  {id: 1, name: 'A', parentId: null},
  {id: 2, name: 'B', parentId: 1},
  {id: 3, name: 'C', parentId: 1},
  {id: 4, name: 'D', parentId: 2},
  {id: 5, name: 'E', parentId: 2},
  {id: 6, name: 'F', parentId: 3},
  {id: 7, name: 'G', parentId: 3},
  {id: 8, name: 'H', parentId: 4},
]

在这个例子中,我们可以看到每个节点都有一个唯一的id,一个名称和一个parentId,parentId是指它的父节点的id,如果没有父节点,parentId为null。让你看看你们这帮小学生的思路和我的有何不同。

function buildTree(nodes) {
  const tree = []
  const map = {}

  nodes.forEach(node => {
    map[node.id] = node
    node.children = []
  })

  nodes.forEach(node => {
    if (node.parentId) {
      map[node.parentId].children.push(node)
    } else {
      tree.push(node)
    }
  })

  return tree
}

看到这里,你不禁感慨,这代码写得真好!这不是小学生可以想到的,只有我这样的神仙才能想到啊!(滑稽)

不过,哪有这么简单的事呢?咳咳,这个代码还可以再优化哦!比如,我们可以减少循环次数,使用哈希表来存储节点,甚至还能使用并发机制来提高代码性能!

当然,如果你在面试的时候讲出了这些高级操作,面试官肯定会惊为天人,毫不犹豫地把 offer 递给你了。当然,如果面试官是我这种幽默的人,听完你的回答,估计会冒出一句:“哇,好厉害啊!可以详细讲讲怎么优化吗?”

减少循环次数

在之前的代码中,我们使用了两个forEach循环来遍历节点数组。但是,这会导致多次循环遍历同一个数组,从而降低性能。为了减少循环次数,我们可以使用单个for循环来遍历节点数组。

以下是修改后的代码:

function buildTree(nodes) {
    
  // 这里新增一个排序,避免节点顺序不对照成的问题
  nodes.sort((a, b) => a.id - b.id)
  
  const tree = []
  const map = {}

  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]
    map[node.id] = node
    node.children = []
    const parentId = node.parentId

    if (!parentId) {
      tree.push(node)
    } else {
      if (!map[parentId]) {
        map[parentId] = { id: parentId, children: [] }
      }
      map[parentId].children.push(node)
    }
  }

  return tree
}


我们将两个forEach循环替换为单个for循环。我们还添加了一个条件语句,以检查节点是否有父节点。如果没有父节点,我们将其添加到树的根节点中,否则我们将其添加到其父节点的children数组中。

使用哈希表

在之前的代码中,我们使用一个映射来存储节点。但是,对于较大的数据集,使用哈希表可以更快地查找节点。因此,我们可以使用JavaScript中的对象来代替映射。

以下是修改后的代码:

function buildTree(nodes) {
  const tree = {}
  const map = {}

  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]
    map[node.id] = node
    node.children = []
    const parentId = node.parentId

    if (!parentId) {
      tree[node.id] = node
    } else {
      if (!map[parentId]) {
        map[parentId] = { id: parentId, children: [] }
      }
      map[parentId].children.push(node)
    }
  }

  return Object.values(tree)
}

我们将tree从数组改为对象,并使用node.id作为键。我们还将映射从Map对象改为普通对象。这样可以使代码更加简洁,并减少了一些额外的函数调用。

将数据进行排序

如果数据是按照id顺序排列的,可以使用二分查找来查找父节点,从而减少遍历的次数。因此,我们可以将节点数组按照id排序,以优化性能。

以下是修改后的代码:

function buildTree(nodes) {
  const tree = {}
  const map = {}
  nodes.sort((a, b) => a.id - b.id)

  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]
    map[node.id] = node
    node.children = []
    const parentId = node.parentId

    if (!parentId) {
      tree[node.id] = node
    } else {
      const parentIndex = binarySearch(nodes, parentId)

      if (parentIndex !== -1) {
        const parentNode = nodes[parentIndex]

        if (!parentNode.children) {
          parentNode.children = []
        }

        parentNode.children.push(node)
      } else {
        if (!map[parentId]) {
          map[parentId] = { id: parentId, children: [] }
        }
        map[parentId].children.push(node)
      }
    }
  }

  return Object.values(tree)
}

function binarySearch(arr, id) {
  let start = 0
  let end = arr.length - 1

  while (start <= end) {
    const mid = Math.floor((start + end) / 2)
    const midId = arr[mid].id

    if (midId === id) {
      return mid
    } else if (midId < id) {
      start = mid + 1
    } else {
      end = mid - 1
    }
  }


### 最后

我可以将最近整理的前端面试题分享出来,其中包含**HTML、CSS、JavaScript、服务端与网络、Vue、浏览器、数据结构与算法**等等,还在持续整理更新中,希望大家都能找到心仪的工作。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

**篇幅有限,仅展示部分截图:**

![](https://img-blog.csdnimg.cn/img_convert/ff6b8e636a356a01389cd2de0211d347.png)

![](https://img-blog.csdnimg.cn/img_convert/b24f32dd81ead796fa80c27c763d262c.png)

![](https://img-blog.csdnimg.cn/img_convert/c6265dc2681708533916a8d7910506b3.png)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值