一、前言

  1. 在我们日常开发中生成树形结构是无可避免的,比如权限管理的层级结构,学校企业的组织结构以及我们日常开发的菜单列表等等。
  2. 我最近看到过一篇文章,在面试的过程中,会被要求手写一下如何根据扁平的数据结构生成一个树形结构,结果却不尽人意只有 20% ~ 30% 的人能够高效并快速的写出,有些人只是知道使用递归,却不能写出,只有在提醒下才勉强写出,更甚至有少部分人在提醒下都不能理解写出。
  3. 甚至在评论区竟然出现了一些恶搞的评论,我也是沉浸在其中:这些东西都是八股文都是应试用的;后端的好兄弟:我们都直接让前端自己处理;前端的 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) => {
       // 创建一个map
       let 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
   }

三、总结

  1. 在日常的编码中,生成树形结构还是很常见的,无论是为了应付面试,还是强化我们的编码思想都是十分的关键的,所以让我们一起掌握树形结构的操作吧,来做我们自己的背锅侠。
  2. 在思考的过程中要多方面的思考,可以结合多门编程语言,每个语言都有每个语言的特色,当时其基本的底层逻辑都是十分相似的,我印象很深刻的点是:在学习Netty的时候发现像一些异步任务、事件循环机制以及Promise, 与浏览器的 EventLoop 和JavaScript中的Promise()都是大同小异的。
    欢迎大家在评论区留下自己的看法!!

更多推荐

AI 告诉你 一行代码生成树形结构