通过递归构造树

背景

构造一棵树,再通过json返回给前端展示是我们经常需要遇到的功能。在这里总结一下通过递归功能构造树,结合着stream,将代码变得简洁易懂。

实体结构

通常的实体类型树结构,为List< List<…> >,最外边为root节点,里边为子子孙孙节点。其中还需要有一个关联关系,子节点可以通过parentId关联父节点。实体类例子如下:
这是一个类名为SlogPolicy的树结构,其中主要用到的属性如下:
在这里插入图片描述
所属层级和路径如有需要可以加上,并不影响树的构造
在这里插入图片描述
在这里插入图片描述

代码

因为比较简单、代码就直接贴图了。但是思路会在下方指出
在这里插入图片描述
上图有三步:

  1. 从数据库中获取到所有需要展示的节点,相当于直接缓存到内存中;
  2. 通过filter方法过滤出父节点,依据为parentId为0;
  3. 通过peek方法在每一个父节点下set子节点。

之后进入获取子节点的递归方法getChildren(),如下:
参数说明:第一个参数就是当前父节点、第二个参数为上述步骤1查询出的数据
在这里插入图片描述

  1. 通过stream对所有数据进行操作
  2. 通过filter方法过滤出所属父节点,依据为子节点的parentId为父节点的id;
  3. 通过peek方法在每一个子节点下再set孙节点。如有需要也可以通过sorted方法对生成的列表排序

递归出口:可以想到,当子节点下再找不到下一级的子节点了,那么这个root节点下的递归构造就完成了,便可回溯。也就是通过filter方法过滤出的数据为空了。

上诉步骤完成后,树结构就构造完成。可以看到通过stream流方式很方便。

这样的方法还可以想到优化的点,因为每次要把所有查到的数据作为参数代入到递归方法中,之后通过stream处理。数据量大的情况下会耗费一定的性能。因此也可以通过:
1、不一次性查询到所有数据缓存到内存中,而是先查到所有父节点数据,再在递归过程中查询到其下的子节点数据。(这样是会照成请求数据库I/O次数较多)。
2、可以将已经set的数据进行清理,这样数据量会逐级递减。

上诉stream流中使用到了peek方法,说明一下与map的区别:

主要区别为:peek为消费型接口,调用peek方法后, 流还在。
map方法是函数型接口。调用map方法后,流已经被消费。
peek对一个对象进行操作的时候,对象不变,但是可以改变对象里面的值
map方法接收一个Function作为入参. Function是有返回值的, 这就表示map对Stream中的元素的操作结果都会返回到Stream中去

四种函数式接口:
在这里插入图片描述

示例代码

import java.util.ArrayList;
import java.util.List;

class Person {
    private int id;
    private String name;
    private int parentId;
    private List<Person> children;

    public Person(int id, String name, int parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
        this.children = new ArrayList<>();
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getParentId() {
        return parentId;
    }

    public List<Person> getChildren() {
        return children;
    }
}

public class TreeBuilder {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person(1, "Alice", 0));
        personList.add(new Person(2, "Bob", 1));
        personList.add(new Person(3, "Charlie", 1));
        personList.add(new Person(4, "David", 2));
        personList.add(new Person(5, "Eve", 2));

        List<Person> treeList = buildTree(personList);
        // 使用 treeList 进行后续操作
    }

    public static List<Person> buildTree(List<Person> personList) {
        List<Person> treeList = new ArrayList<>();

        personList.stream()
                .filter(person -> person.getParentId() == 0)
                .peek(treeList::add)
                .forEach(root -> addChildren(root, personList));

        return treeList;
    }

    private static void addChildren(Person parent, List<Person> personList) {
        personList.stream()
                .filter(person -> person.getParentId() == parent.getId())
                .peek(parent.getChildren()::add)
                .forEach(child -> addChildren(child, personList));
    }
}

使用 Stream 流的 filter() 方法来筛选根节点和子节点,并使用 peek() 方法将节点添加到相应的列表中。在 buildTree() 方法中,我们首先筛选出根节点(parentId 为 0),并将它们添加到 treeList 中。然后,通过调用 addChildren() 方法递归地添加每个根节点的子节点。

在 addChildren() 方法中,我们使用 Stream 流的 filter() 方法筛选出每个父节点的子节点,并将它们添加到父节点的 children 列表中。然后,再递归地调用 addChildren() 方法,以添加每个子节点的子节点,直到所有节点被处理完毕。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值