首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

面试题——列表转树的深度解析与实战技巧

  • 24-12-16 16:03
  • 2229
  • 5741
juejin.cn

一、理解列表的本质

什么是列表?

13.jpg

列表本质上是一个数组,它作为一种容器,能够存储一系列元素。这些元素可以是任何类型的值,包括但不限于数字、字符串、对象等。数组的特点在于其连续性——每个元素都按顺序排列,并且可以通过下标直接访问。例如:

js
代码解读
复制代码
const arr = [1, "2", { a: 1 }];

在这个例子中,arr[0] 是 1,arr[1] 是 "2",而 arr[2] 是一个对象 { a: 1 }。这种灵活性使得列表成为了一种不可或缺的数据容器。

对于本题而言,我们的列表由若干个具有 id 和 title 属性的对象组成,同时每个对象还包含了一个 parentId 属性,用于标识其父节点。这样的设计为我们构建树形结构提供了坚实的基础。

js
代码解读
复制代码
let list = [ { id: '1', title: '节点1', parentId: '', }, { id: '1-1', title: '节点1-1', parentId: '1' }, { id: '1-2', title: '节点1-2', parentId: '1' }, { id: '2', title: '节点2', parentId: '' }, { id: '2-1', title: '节点2-1', parentId: '2' } ]

二、递归方法实现列表转树

要将一个平面列表转换为树形结构,我们可以采用递归的方法。递归是一种强大的工具,它通过将大问题分解为更小的问题来逐步解决复杂问题。在列表转树的情境下,递归的核心思想是:

  • 大问题是啥? 如何将一个平面的所有节点转变成树型结构?
  • 小问题是啥? 如何将 parentId 为某值的节点,转成树型结构?

具体来说,我们需要编写一个函数 listToTree,它接受两个参数:一个是待转换的列表 list,另一个是指定的 parentId。该函数会遍历整个列表,找出所有 parentId 匹配的节点,并将它们作为子节点添加到对应的父节点之下。

js
代码解读
复制代码
function listToTree(list, parentId = "") { return list.filter(item => item.parent === parentId) .map(item => ({ ...item, children: listToTree(list, item.id) })); } console.log(listToTree(list));

调用 listToTree(list) 即可开始转换过程,这里假设根节点的 parentId 为空字符串 ''。

image.png

为了得到更详细的信息,可以使用 JSON.stringify 方法格式化输出结果:

js
代码解读
复制代码
console.log(JSON.stringify(list2Tree(list), null, 2));

得到如下图的详细信息,这不仅提高了输出的可读性,还便于调试和分析。

image.png

JSON.stringify 方法这是一个常用的 JavaScript 语句,用于将一个复杂的对象或数组以格式化的 JSON 字符串形式输出到控制台。 它有三个参数:

  1. value:要转换的对象或数组。
  2. replacer(可选) :一个函数或数组,用来选择或修改对象的属性值。如果是一个函数,则会调用该函数来处理每个键值对;如果是数组,则只包含这些键名的属性会被序列化;如果不提供此参数,则所有属性都会被序列化。
  3. space(可选) :用于控制缩进和空格的字符串或数字。如果是一个数字,则表示每一层级前添加的空格数;如果是一个字符串(最多 10 个字符),则直接使用该字符串进行缩进,这使得生成的 JSON 字符串更易读。

三、性能优化之道

时间复杂度 vs 空间换时间

虽然上述递归方法直观易懂,但它的效率并不高。每次递归调用都会重新遍历整个列表,导致时间复杂度达到 O(n²)。为了提高性能,我们可以利用哈希表来存储每个节点的信息,从而实现线性查找。

js
代码解读
复制代码
function listToTree(list) { const map = {}; //空间换时间 json const root = []; // 初始化 map,将每个节点的 id 作为键,值为节点对象,并将 children 初始化为空数组 list.forEach((item) => { //map 初始化, O(1) map[item.id] = { ...item, children: [] }; }) // 遍历列表,将每个节点添加到其父节点的 children 数组中 list.forEach(item => { if (item.parentId) { // 如果节点有父节点,则将其添加到父节点的 children 数组中 if (map[item.parentId]) { map[item.parentId].children.push(map[item.id]); } } else { // 如果节点没有父节点,则将其作为根节点 root.push(map[item.id]); } }); return root; } console.log(listToTree(list)); console.log(JSON.stringify(listToTree(list), null, 2));

在这里我们首先创建一个哈希表 Map 来存储每个节点的信息、root用于存储所有的根节点,然后用 forEach 方法遍历 list 数组中的每一个元素,对于每个节点,我们创建一个新的对象,复制了原对象的所有属性,并添加了一个空的 children 数组,然后将这个新对象存入 map 中,键为节点的 id。经过这一步,map 包含了所有节点的信息,并且每个节点都有一个空的 children 数组。

再次遍历列表,判断是否有父节点,有父节点的情况:使用 if (item.parentId) 检查当前节点是否有父节点(即 parentId 是否存在且不为 null 或 undefined),再使用 if (map[item.parentId]) 确保父节点确实存在于 map 中,以避免访问不存在的键导致的错误,然后将当前节点添加到其父节点的 children 数组中;无父节点的情况:如果 parentId 是 null 或 undefined,说明这是一个根节点,将该节点直接推入 root 数组中,这就是我们构建好的树形结构。

四、结语

掌握列表转树的技巧,不仅是面试中的加分项,更是日常工作中不可或缺的能力。希望今天的分享能为大家提供一些启发,帮助大家更加熟练地处理复杂的数据结构!

16.jpg

注:本文转载自juejin.cn的池鱼ipou的文章"https://juejin.cn/post/7448440558270087205"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

142
代码人生
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top