一、前言
- 在我们日常开发中生成树形结构是无可避免的,比如权限管理的层级结构,学校企业的组织结构以及我们日常开发的菜单列表等等。
- 我最近看到过一篇文章,在面试的过程中,会被要求手写一下如何根据扁平的数据结构生成一个树形结构,结果却不尽人意只有 20% ~ 30% 的人能够高效并快速的写出,有些人只是知道使用递归,却不能写出,只有在提醒下才勉强写出,更甚至有少部分人在提醒下都不能理解写出。
- 甚至在评论区竟然出现了一些恶搞的评论,我也是沉浸在其中:这些东西都是八股文都是应试用的;后端的好兄弟:我们都直接让前端自己处理;前端的 homie :后端返回给我们的都是树形结构我们不需要处理,只需要渲染一下就可以了。What?我是甩锅侠!!!
这篇文章将会使用 Java 和 JavaScript 以及结合 ChatGPT 和我对生成树形结构的理解,结合 JavaScript 和Java 的语法糖来生成一下树形结构,从此让我们走向背锅侠的道路 ( doge ) 。
二、生成树形结构的方式
1、 数据准备
首先我们初始化一些数据,用来为我们之后生成树形结构作基础,这里的结构都是很简单的,只要会一点面向对象的基础都可以很容易的生成, 为了简单起见,我将去除其中一些复杂的操作,比如Java中各种O的转化,以及真实场景中的复杂的业务逻辑,只专注于根据 id 和 pid 生成树形结构。
2、递归方式
温馨提示 : 在编码过程中,我使用了 Java 和 JavaScript 中的一些语法糖,比如 Java 中的 Stream 流操作以及 JavaScript 中的一些 数组的 forEach()、map()、filter()、find(),数组以及对象的解构赋值等等,如果你还没有掌握的话,需要抓紧了,因为这是在开发中再常见不过的方法了。
使用递归的方式其实是对语言本身方法栈的调用:主要抓住三个点:
(1)我们在理解问题的时候不能钻进程序具体调用的细节中去,我们要从宏观进行理解,如果你要
进行断点调试,在同一个方法中反复横跳,不晕才怪。
(2)我们要将大的问题拆解为小的子问题,小的子问题和大的问题调用过程是相同的。
(3)我们要找到最小子问题的递归出口(结束条件)。
代码示例:
public static List<TreeNode> toTreeRecursion(Long pid, List<TreeNode> list) {ArrayList<TreeNode> result = new ArrayList<>();list.forEach(item -> {if (item.getPid().equals(pid)) {result.add(item);}});// 递归出口result.forEach(item -> item.setChildren(toTreeRecursion(item.getId(), list)));return result;}
const createTreeRecursion = (id,list) => {let children = list.filter(item => id === item.pid)children.forEach(child => {child.children = createTree(child.id,list)})return children}
当我们没有头绪的时候我们可以寻求智能 AI 的帮助,可能会有意想不到的收获:
3、一行代码递归方式
我们该如何简化我们的代码,我们可以寻求我们的好兄弟 ChatGPT 的帮助。
这种的代码结构其实是有很多种的实现,你可以根据自己的想法进行任意转化,比如下面是我的想法:
能写出机器理解的代码很容易,但是我们需要写出人能够理解的代码。同时我们以看出 ChatGPT也是不推荐我们去这样写。与此同时,在复杂的业务逻辑中写出这样的代码也是不可能的。
4、Map方式
在使用递归的方式进行生成树形结构时,可以从宏观的理解生成树形结构的过程,但是树形结构毕竟是栈的重复调用,时间复杂度为 log(2^n) ,随着数据量的增多,还是需要进行优化的,我们可以使用 Map 的方式,使其时间复杂度达到 log(n) 。
代码示例:
public static List<TreeNode> toTreeMap(List<TreeNode> list) {// 第一次遍历Map<Long, TreeNode> map = list.stream().collect(Collectors.toMap(TreeNode::getId, item -> item));List<TreeNode> result = new ArrayList<>();// 第二次遍历list.forEach(item -> {if (map.get(item.getPid()) == null) {result.add(item);} else {List<TreeNode> children = item.getChildren();if (children == null) {item.setChildren(new ArrayList<>());}map.get(item.getPid()).getChildren().add(item);}});return result;
}
const crateTreeMap = (data) => {// 创建一个maplet map = {}// 第一次遍历data.forEach(item => {map[item.id] = item})// 第二次遍历let result = []data.forEach(item => {let parent = map[item['pid']]if (parent) {(parent.children || (parent.children = [])).push(item)}else {result.push(item)}})return result}
三、总结
- 在日常的编码中,生成树形结构还是很常见的,无论是为了应付面试,还是强化我们的编码思想都是十分的关键的,所以让我们一起掌握树形结构的操作吧,来做我们自己的背锅侠。
- 在思考的过程中要多方面的思考,可以结合多门编程语言,每个语言都有每个语言的特色,当时其基本的底层逻辑都是十分相似的,我印象很深刻的点是:在学习Netty的时候发现像一些异步任务、事件循环机制以及Promise, 与浏览器的 EventLoop 和JavaScript中的Promise()都是大同小异的。
欢迎大家在评论区留下自己的看法!!