当前位置: 首页 > news >正文

java构建树形结构的方式、如何组装树状结构数据

文章目录

      • 思考思路
      • 实现:Java构建树状结构数据
        • 1. 递归方式
        • 2. 利用Stream流
        • 3. 基于Map(推荐)
          • 3.1 基于Map方式分析
        • 4. 总结

思考思路

我在考虑如何在Java中构建树状数据结构:常见的方法包括递归、父子引用和使用节点列表等。可以通过创建树节点类来表示树形结构,例如,可以定义“树节点”类为:class Node { T data; List<Node> children; Node parent; }

我将提供详细分析,看看哪些方法在实际项目中更常见,如何构建包含父子关系和操作树的数据结构。

这个问题的答案可能会包括多个常见的实现方式。

在 Java 中构建树状结构,常见方法包括:1. 递归:从父子关系数据递归构建树;2. 使用 Map/HashMap:存储节点并根据父子关系链接它们。我可以提供代码示例,如定义TreeNode 类如下:

public class TreeNode<T> {
    private T data;
    private List<TreeNode<T>> children = new ArrayList<>();
    public TreeNode(T data) {
        this.data = data;
    }
    public void addChild(TreeNode<T> child) {
         children.add(child);
    }
}

实现:Java构建树状结构数据

目的: 在 Java 中构建树状结构数据是一个非常常见的需求,通常用于展示分层数据(如:组织架构、菜单、文件目录等),下面详细介绍几种常见的方法、代码案例及实际项目中的实践经验。比如实现这样的分层效果:

这些数据都在数据库,所以查数据库:查出所有的数据,然后再组装返回给前端解析即可,当然数据表中应该设计成这样:

在这里插入图片描述

组装时只需要组装成一个 list 返回即可,前端拿到 list 解析。

1. 递归方式

原理: 递归方法基于“父子关系”的层级遍历,从根节点开始递归查找和添加子节点。适合节点层次较少的场景。

实现: 利用递归的方式进行树状结构数据的组装,代码如下:

@Data
@NoArgsConstructor
public class DeptEntity {
    private Long deptId;
    private Long parentId;
    private String deptName;
    private List<DeptEntity> children;

    public DeptEntity(Long deptId, Long parentId, String deptName) {
        this.deptId = deptId;
        this.parentId = parentId;
        this.deptName = deptName;
    }
}
public class Test {
    // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据
    public static List<DeptEntity> getData() {
        List<DeptEntity> list = new ArrayList<>();
        list.add(new DeptEntity(100L, 0L, "若依科技"));
        list.add(new DeptEntity(101L, 100L, "深圳总公司"));
        list.add(new DeptEntity(102L, 100L, "长沙分公司"));
        list.add(new DeptEntity(103L, 101L, "研发部门"));
        list.add(new DeptEntity(104L, 101L, "市场部门"));
        list.add(new DeptEntity(105L, 102L, "运维部门"));
        list.add(new DeptEntity(106L, 102L, "财务部门"));
        return list;
    }

    public static void main(String[] args) {
        recursionBuildTree();
    }

    // 方法1:递归
    public static void recursionBuildTree() {
        // 获取假数据
        List<DeptEntity> data = getData();
        // 留住最顶层节点
        List<DeptEntity> collect = data.stream()
                .filter(d -> d.getParentId() == 0L)
                .collect(Collectors.toList());
        //首先设置最顶层子节点
        for (DeptEntity deptEntity : collect) {
            deptEntity.setChildren(recursionBuildChildren(deptEntity.getDeptId(), data));
        }
        System.out.println(collect);
    }

    // 设置子节点
    public static List<DeptEntity> recursionBuildChildren(Long deptId, List<DeptEntity> data) {
        // 保存子节点
        List<DeptEntity> children = new ArrayList<>();
        // 寻找子节点
        for (DeptEntity deptEntity : data) {
            // 因为data是最原始数据,还是需要过滤掉最顶层节点
            if (deptEntity.getParentId() == 0L) {
                continue;
            }
            if (deptEntity.getParentId().equals(deptId)) {
                children.add(deptEntity);
            }
        }
        //递归遍历子节点
        for (DeptEntity deptEntity : children) {
            deptEntity.setChildren(recursionBuildChildren(deptEntity.getDeptId(), data));
        }
        return children;
    }
}
2. 利用Stream流

1、上面的 DeptEntity 类代码不变

2、写方法:

public class Test {
    // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据
    public static List<DeptEntity> getData() {
        List<DeptEntity> list = new ArrayList<>();
        list.add(new DeptEntity(100L, 0L, "若依科技"));
        list.add(new DeptEntity(101L, 100L, "深圳总公司"));
        list.add(new DeptEntity(102L, 100L, "长沙分公司"));
        list.add(new DeptEntity(103L, 101L, "研发部门"));
        list.add(new DeptEntity(104L, 101L, "市场部门"));
        list.add(new DeptEntity(105L, 102L, "运维部门"));
        list.add(new DeptEntity(106L, 102L, "财务部门"));
        return list;
    }

    public static void main(String[] args) {
       streamBuildTree();
    }

    // 方法2:Stream流
    public static void streamBuildTree() {
        List<DeptEntity> data = getData();
        // 按照parentId分组,将数据转为Map<parentId, 属于这个parentId的数据的一个集合>
        Map<Long, List<DeptEntity>> collect = data.stream()
                .collect(Collectors.groupingBy(DeptEntity::getParentId));

        // 开始构建树
        List<DeptEntity> list = streamBuildChildren(0L, collect);
        System.out.println(list);
    }

    // 设置子节点
    public static List<DeptEntity> streamBuildChildren(Long parentId, Map<Long, List<DeptEntity>> collect) {
        return Optional.ofNullable(collect.get(parentId))
                .orElse(new ArrayList<>()) // 避免报空指针异常
                .stream()
                .peek(child -> child.setChildren(streamBuildChildren(child.getDeptId(), collect)))
                .collect(Collectors.toList());
    }

}
3. 基于Map(推荐)

原理: 首先将所有节点存入一个哈希表(Map),键为节点的 id。然后遍历所有节点,根据其 parentId 找到对应父节点并加入到父节点的子节点列表中。这样可以避免递归的性能问题。

好处: 在 Java 中,使用 Map 来构建树结构是一种高效的方法。该方法避免了递归的性能问题,并且可以快速查找节点,提高构建树的效率。

实现: 如下:

1、上面的 DeptEntity 类代码不变

2、写方法:

public class Test {
    // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据
    public static List<DeptEntity> getData() {
        List<DeptEntity> list = new ArrayList<>();
        list.add(new DeptEntity(100L, 0L, "若依科技"));
        list.add(new DeptEntity(101L, 100L, "深圳总公司"));
        list.add(new DeptEntity(102L, 100L, "长沙分公司"));
        list.add(new DeptEntity(103L, 101L, "研发部门"));
        list.add(new DeptEntity(104L, 101L, "市场部门"));
        list.add(new DeptEntity(105L, 102L, "运维部门"));
        list.add(new DeptEntity(106L, 102L, "财务部门"));
        return list;
    }

    public static void main(String[] args) {
        mapBuildTree();
    }

    // 方法3:基于Map 1
    public static void mapBuildTree() {
        // 获取假数据
        List<DeptEntity> data = getData();

        // 存放根节点
        List<DeptEntity> roots = new ArrayList<>();
        // 存放所有子节点
        Map<Long, List<DeptEntity>> childMap = new HashMap<>();

        // 1.初始化childMap,确保不会出现null
        for (DeptEntity deptEntity : data) {
            childMap.put(deptEntity.getDeptId(), new ArrayList<>());
        }
        // 2.构建树结构
        for (DeptEntity deptEntity : data) {
            // 根节点
            if (deptEntity.getParentId() == null || deptEntity.getParentId() == 0) {
                roots.add(deptEntity);
            } else {
                // 非根节点,加入到父节点的children列表
                childMap.get(deptEntity.getParentId()).add(deptEntity);
            }
        }
        // 3.统一赋值children
        for (DeptEntity deptEntity : data) {
            deptEntity.setChildren(childMap.get(deptEntity.getDeptId()));
        }

        System.out.println(roots);

    }
}

基于 Map 的方式还有一个,如下:

public class DeptEntity {
    private Long deptId;
    private Long parentId;
    private String deptName;
    private List<DeptEntity> children = new ArrayList<>();

    // 构造方法
    public DeptEntity(Long deptId, Long parentId, String deptName) {
        this.deptId = deptId;
        this.parentId = parentId;
        this.deptName = deptName;
    }

    // Getter & Setter
    public Long getDeptId() { return deptId; }
    public Long getParentId() { return parentId; }
    public String getDeptName() { return deptName; }
    public List<DeptEntity> getChildren() { return children; }
    
    // 加一个这个方法
    public void addChild(DeptEntity child) {
        this.children.add(child);
    }
}
public class Test {
    // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据
    public static List<DeptEntity> getData() {
        List<DeptEntity> list = new ArrayList<>();
        list.add(new DeptEntity(100L, 0L, "若依科技"));
        list.add(new DeptEntity(101L, 100L, "深圳总公司"));
        list.add(new DeptEntity(102L, 100L, "长沙分公司"));
        list.add(new DeptEntity(103L, 101L, "研发部门"));
        list.add(new DeptEntity(104L, 101L, "市场部门"));
        list.add(new DeptEntity(105L, 102L, "运维部门"));
        list.add(new DeptEntity(106L, 102L, "财务部门"));
        return list;
    }

    public static void main(String[] args) {
        mapBuildTree();
    }

    // 方法3:基于Map 2
    public static void mapBuildTree() {
        // 获取假数据
        List<DeptEntity> data = getData();

        List<DeptEntity> roots = new ArrayList<>();
        Map<Long, DeptEntity> childMap = new HashMap<>();

        // 1.将所有节点存入Map中
        for (DeptEntity deptEntity : data) {
            childMap.put(deptEntity.getDeptId(), deptEntity);
        }
        // 2.构建树结构
        for (DeptEntity deptEntity : data) {
            // 根节点
            if (deptEntity.getParentId() == null || deptEntity.getParentId() == 0) {
                roots.add(deptEntity);
            } else {
                // 非根节点,加入到父节点的children列表
                DeptEntity parent = map.get(deptEntity.getParentId());
                if (parent != null) {
                    parent.addChild(deptEntity);
                }
            }
        }
        
        System.out.println(roots);

    }
}
3.1 基于Map方式分析

1、代码分析:

  • 先将所有节点存入 Map<Long, DeptEntity>,便于快速查找。
  • 遍历 data:
    1、如果 parentId 为 null 或 0,表示它是根节点,加入 roots 列表。
    2、否则,在 map 中查找 parentId 对应的父节点,并将该节点加入父节点的 children 列表。

2、优势分析:
(1)时间复杂度:遍历列表 data 两次,因此时间复杂度为 O(n),相比递归构建树(可能达到 O(n²))效率更高。

(2)空间复杂度:使用了 Map<Long, DeptEntity>,其空间复杂度是 O(n),额外开销较小。

(3)避免递归问题:采用 Map 直接查找父节点,避免递归调用,避免栈溢出问题,特别适用于深度较大的树结构。

3、适合场景:

✅ 数据量大,如百万级别的分类、菜单、组织架构等。
✅ 层级较深,如文件目录、权限管理系统。
✅ 频繁查询,构建后可以缓存树,提高性能。

4、不适合场景:

❌ 数据量特别小(<100),可以直接使用递归方法,代码更简洁。

4. 总结

在这里插入图片描述

至此,文章结束,主页还有更多精彩文章哦!!!

相关文章:

  • ModBus TCP/RTU互转(主)(从)|| Modbus主动轮询下发的工业应用 || 基于智能网关的串口服务器进行Modbus数据收发的工业应用
  • 课程4. 图像处理算法与卷积神经网络
  • js核心运行逻辑
  • Vue的虚拟DOM算法准确来讲应该是叫做“snabbdom”或者--“diff算法”之类
  • 分布式全局ID生成方案
  • 集成电路设计、制造和测试之旅
  • Lua 数据类型
  • vue2实现可拖拽菜单栏,及根据菜单内容自动扩展宽度
  • 论文阅读:2023-arxiv Can AI-Generated Text be Reliably Detected?
  • Ubuntu22.04虚拟机里安装Yolov8流程
  • 使用redis客户端中对于json数据格式的存储和读取
  • 在LORA训练中,LORA模型的矩阵的行列是多少
  • 【ARM中R0寄存器】
  • 基于k3s部署Nginx、MySQL、Golang和Redis的详细教程
  • 文件解析漏洞
  • 协程池是调用端并发请求的缓释胶囊
  • 每日定投40刀BTC(9)20250312 - 20250315
  • 剑指 Offer II 078. 合并排序链表
  • 一对一交友App源码开发新趋势:精准匹配与多元盈利模式解析
  • 微服务即时通信系统---(八)用户管理子服务
  • 发出“美利坚名存实亡”呼号的卡尼,将带领加拿大走向何方?
  • 中国体育报关注徐梦桃、王曼昱、盛李豪等获评全国先进工作者:为建设体育强国再立新功
  • 《奇袭白虎团》原型人物之一赵顺合辞世,享年95岁
  • 国家发改委答澎湃:将指导限购城市针对长期摇号家庭和无车家庭等重点群体定向增发购车指标
  • 伊朗爆炸港口已恢复货物进出口工作
  • 来论|如何看待韩企在美申请“饺子”专利