深入解析 Java 递归:构建层级树形结构的优雅实现!!!

🚀 深入解析 Java 递归:构建层级树形结构的优雅实现 🔧

大家好!👋 今天我们来聊聊 Java 中一个非常常见的操作:通过递归构建层级树形结构。🎉 具体来说,我们将深入分析以下代码片段:

// 递归构建子树
for (InviteCode child : children) {
    InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap);
    node.getChildren().add(childNode);
}

这段代码是构建邀请码层级树的一部分,背后涉及了递归、树形数据结构以及 Java 的集合操作。💡 我们将从代码的背景开始,逐步拆解它的实现原理,探讨使用场景、优势和优化方法,最后通过一个实际案例展示它的应用。为了更直观地理解整个过程,我们还会插入一个 Mermaid 流程图!📊

准备好了吗?让我们开始吧!🚀


📖 背景:为什么需要递归构建树形结构?

在 Java 开发中,我们经常需要处理层级数据。例如,在一个邀请码系统中,我们有一个 List<InviteCode>,其中 InviteCode 是一个实体类,包含以下字段:

public class InviteCode {
    private Integer id;
    private String inviteCode;
    private Integer inviteLevel;
    private Integer createdBy;

    // Getters and Setters
    public Integer getId() {
        return id;
    }

    public String getInviteCode() {
        return inviteCode;
    }

    public Integer getInviteLevel() {
        return inviteLevel;
    }

    public Integer getCreatedBy() {
        return createdBy;
    }
}

假设我们有一个 List<InviteCode>,包含 adminId = 7 的所有邀请码记录:

idadmin_idcreated_byinvite_codeinvite_level
207NULL******0
217202631131
227207043581
237209828681
247NULL******0
25724******1
26725******2
277269914763

我们的目标是构建一个以 adminId 为根的邀请码层级树。层级树的构建需要从根节点(createdBy == null)开始,递归地找到每个节点的子节点(createdBy 等于父节点 id)。最终,我们需要将数据转换为树形结构(InviteCodeTreeDTO),以便前端展示:

public class InviteCodeTreeDTO {
    private Integer id;
    private String inviteCode;
    private Integer inviteLevel;
    private List<InviteCodeTreeDTO> children;

    // Getters and Setters
}

buildTree 方法中,我们已经为当前节点(root)创建了一个 InviteCodeTreeDTOnode),并找到了它的直接子节点(children)。现在,我们需要递归地为每个子节点构建子树,这就是以下代码的作用:

// 递归构建子树
for (InviteCode child : children) {
    InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap);
    node.getChildren().add(childNode);
}

🌟 代码拆解:一步步理解

让我们逐步拆解这段代码,弄清楚它是如何工作的!

1. for (InviteCode child : children)

  • 增强型 for 循环:遍历 children 列表中的每个 InviteCode 对象。
    • children:一个 List<InviteCode>,包含当前节点 root 的所有直接子节点(createdBy 等于 root.getId()InviteCode 对象)。
    • child:每次循环中代表一个子节点(例如 id = 21createdBy = 20)。
  • 作用:为每个子节点执行后续逻辑,生成对应的子树。

2. InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap);

  • buildTree:递归调用 buildTree 方法,传入子节点 childinviteCodeMap
    • child:当前处理的子节点(例如 id = 21)。
    • inviteCodeMap:一个 Map<Integer, InviteCode>,用于快速查找 InviteCode 对象。
  • 作用buildTree 方法会递归地构建以 child 为根的子树,返回一个 InviteCodeTreeDTO
  • 递归逻辑buildTree 会:
    1. 创建一个新的 InviteCodeTreeDTOchildNode),设置 idinviteCodeinviteLevel
    2. 查找 child 的子节点(createdBy = child.getId())。
    3. 递归调用 buildTree 为每个子节点构建子树。
    4. 返回完整的子树。

3. node.getChildren().add(childNode)

  • node.getChildren():获取当前节点 nodechildren 列表(类型为 List<InviteCodeTreeDTO>)。
  • .add(childNode):将 buildTree 返回的子树(childNode)添加到 nodechildren 列表中。
  • 作用:将子树附加到当前节点的 children 中,构建完整的树形结构。

4. 整体效果

  • 遍历 children 中的每个子节点(InviteCode)。
  • 为每个子节点递归调用 buildTree,生成对应的子树(InviteCodeTreeDTO)。
  • 将所有子树添加到当前节点 nodechildren 列表中。
  • 结果node 包含了以 root 为根的完整树形结构,包括所有子节点及其子树。

📊 Mermaid 流程图:可视化递归过程

为了更直观地理解递归构建树形结构的过程,我们使用 Mermaid 流程图来展示:

开始节点 id: 20
获取子节点 子节点id: 21,22,23
遍历每个子节点
处理子节点 id:21
递归构建树 buildTree(child)
创建子节点对象 id:21
添加子节点到父节点
处理子节点 id:22
递归构建树 buildTree(child)
创建子节点对象 id:22
添加子节点到父节点
处理子节点 id:23
递归构建树 buildTree(child)
创建子节点对象 id:23
添加子节点到父节点
完成构建 父节点id:20 子节点: [21,22,23]
  • 流程说明
    1. 从当前节点 nodeid = 20)开始,获取其子节点(children)。
    2. 对每个子节点(id = 21, 22, 23):
      • 递归调用 buildTree,生成子树(childNode)。
      • 将子树添加到 node.children 中。
    3. 最终,node 包含所有子树。

📝 示例:具体数据

假设 inviteCodeMap 包含以下数据(adminId = 7):

idadmin_idcreated_byinvite_codeinvite_level
207NULL******0
217202631131
227207043581
237209828681
247NULL******0
25724******1
26725******2
277269914763

1. 当前节点:root = id = 20

  • children:包含 id = 21, 22, 23createdBy = 20)。
  • 初始化nodeInviteCodeTreeDTO(id=20, inviteCode="******", inviteLevel=0, children=[])
第一次循环:child = id = 21
  • buildTree(child, inviteCodeMap)
    • childNodeInviteCodeTreeDTO(id=21, inviteCode="263113", inviteLevel=1, children=[])
    • id = 21 没有子节点(createdBy = 21InviteCode 不存在)。
  • node.getChildren().add(childNode)node.children 包含 id = 21 的子树。
第二次循环:child = id = 22
  • buildTree(child, inviteCodeMap)
    • childNodeInviteCodeTreeDTO(id=22, inviteCode="704358", inviteLevel=1, children=[])
    • id = 22 没有子节点。
  • node.getChildren().add(childNode)node.children 包含 id = 21, 22 的子树。
第三次循环:child = id = 23
  • buildTree(child, inviteCodeMap)
    • childNodeInviteCodeTreeDTO(id=23, inviteCode="982868", inviteLevel=1, children=[])
    • id = 23 没有子节点。
  • node.getChildren().add(childNode)node.children 包含 id = 21, 22, 23 的子树。

结果node 是一个完整的树:

  • id = 20
  • children 包含 id = 21, 22, 23 的子树。

2. 当前节点:root = id = 24

  • children:包含 id = 25createdBy = 24)。
  • 初始化nodeInviteCodeTreeDTO(id=24, inviteCode="******", inviteLevel=0, children=[])
第一次循环:child = id = 25
  • buildTree(child, inviteCodeMap)
    • childNodeInviteCodeTreeDTO(id=25, inviteCode="******", inviteLevel=1, children=[])
    • id = 25 有子节点(id = 26createdBy = 25)。
    • 递归调用 buildTree
      • id = 26InviteCodeTreeDTO(id=26, inviteCode="******", inviteLevel=2, children=[])
      • id = 26 有子节点(id = 27createdBy = 26)。
      • 递归调用 buildTree
        • id = 27InviteCodeTreeDTO(id=27, inviteCode="991476", inviteLevel=3, children=[])
        • id = 27 没有子节点。
      • id = 26children 包含 id = 27 的子树。
    • id = 25children 包含 id = 26 的子树。
  • node.getChildren().add(childNode)node.children 包含 id = 25 的子树。

结果node 是一个完整的树:

  • id = 24
  • children 包含 id = 25 -> 26 -> 27 的子树。
开始节点 id: 24
获取子节点 子节点id: 25
遍历子节点
当前子节点 id:25
递归构建子树 buildTree(child)
创建子节点对象 id:25
获取三级子节点 子节点id:26
遍历三级子节点
当前子节点 id:26
递归构建子树 buildTree(child)
创建子节点对象 id:26
获取四级子节点 子节点id:27
遍历四级子节点
当前子节点 id:27
递归构建子树 buildTree(child)
创建叶子节点 id:27
空子节点检测 终止递归
返回叶子节点 id:27
添加子节点 id26.children=[27]
返回三级节点 id26完成
添加子节点 id25.children=[26]
返回二级节点 id25完成
添加子节点 id24.children=[25]
完成树构建 最终结构: 24 →25 →26 →27

🌟 为什么需要递归?

  • 层级结构:邀请码数据可能有多层(例如 id = 24 -> 25 -> 26 -> 27),需要递归地处理每一层。
  • 树形输出:前端需要树形结构(InviteCodeTreeDTO),递归可以自然地构建这种嵌套结构。
  • 通用性:递归方法适用于任意深度的层级数据。

最终 JSON 输出(以 adminId = 7 为例):

{
  "code": 0,
  "msg": "查询成功",
  "data": {
    "adminId": 7,
    "children": [
      {
        "id": 20,
        "inviteCode": "******",
        "inviteLevel": 0,
        "children": [
          {
            "id": 21,
            "inviteCode": "263113",
            "inviteLevel": 1,
            "children": []
          },
          {
            "id": 22,
            "inviteCode": "704358",
            "inviteLevel": 1,
            "children": []
          },
          {
            "id": 23,
            "inviteCode": "982868",
            "inviteLevel": 1,
            "children": []
          }
        ]
      },
      {
        "id": 24,
        "inviteCode": "******",
        "inviteLevel": 0,
        "children": [
          {
            "id": 25,
            "inviteCode": "******",
            "inviteLevel": 1,
            "children": [
              {
                "id": 26,
                "inviteCode": "******",
                "inviteLevel": 2,
                "children": [
                  {
                    "id": 27,
                    "inviteCode": "991476",
                    "inviteLevel": 3,
                    "children": []
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

🚀 优势:为什么使用递归?

1. 代码简洁

  • 递归将复杂的层级结构分解为简单的重复操作,代码更简洁。
  • 对比非递归写法(需要手动维护栈或队列),递归更直观。

2. 通用性强

  • 递归方法可以处理任意深度的层级数据,无需提前知道树的深度。

3. 易于扩展

  • 如果需要添加额外的逻辑(例如过滤子节点),只需在 buildTree 中修改即可。

🛠️ 优化建议

1. 添加循环检测

递归过程中可能存在循环引用(例如 id = 20createdBy 指向 id = 21,而 id = 21 又指向 id = 20)。可以添加循环检测:

private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Set<Integer> visited) {
    if (!visited.add(root.getId())) {
        throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());
    }

    InviteCodeTreeDTO node = new InviteCodeTreeDTO();
    node.setId(root.getId());
    node.setInviteCode(root.getInviteCode());
    node.setInviteLevel(root.getInviteLevel());
    node.setChildren(new ArrayList<>());

    List<InviteCode> children = inviteCodeMap.values().stream()
            .filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId()))
            .collect(Collectors.toList());

    for (InviteCode child : children) {
        InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, new HashSet<>(visited));
        node.getChildren().add(childNode);
    }

    return node;
}
  • 调用时
    for (InviteCode root : roots) {
        InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, new HashSet<>());
        trees.add(tree);
    }
    
  • 效果:避免死循环。

2. 并行处理

如果 children 非常大,可以使用 parallelStream()

children.parallelStream()
    .map(child -> buildTree(child, inviteCodeMap))
    .forEach(childNode -> node.getChildren().add(childNode));
  • 注意:并行流适合大数据量,但在小数据量下可能有性能开销。

3. 日志记录

添加日志,跟踪子树的构建:

for (InviteCode child : children) {
    logger.info("Building subtree for child node: {}", child.getId());
    InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap);
    node.getChildren().add(childNode);
}
  • 效果:便于调试和监控。

📝 完整代码:实际应用

以下是完整的 InviteCodeService 实现,展示了如何使用递归构建层级树:

public class InviteCodeService {
    private final InviteCodeRepository inviteCodeRepository;
    private static final Logger logger = LoggerFactory.getLogger(InviteCodeService.class);

    public InviteCodeService(InviteCodeRepository inviteCodeRepository) {
        this.inviteCodeRepository = inviteCodeRepository;
    }

    public AdminInviteCodeTreeDTO getAdminInviteCodeTree(Integer adminId) {
        List<InviteCode> inviteCodes = inviteCodeRepository.findByAdminId(adminId);
        if (inviteCodes.isEmpty()) {
            AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
            result.setAdminId(adminId);
            result.setChildren(Collections.emptyList());
            return result;
        }

        // 将 List<InviteCode> 转换为 Map<Integer, InviteCode>
        Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
                .collect(Collectors.toMap(InviteCode::getId, ic -> ic));

        // 预构建 createdBy 到子节点的映射
        Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream()
                .filter(ic -> ic.getCreatedBy() != null)
                .collect(Collectors.groupingBy(InviteCode::getCreatedBy));

        // 找到所有根节点(createdBy = NULL)
        List<InviteCode> roots = inviteCodes.stream()
                .filter(ic -> ic.getCreatedBy() == null)
                .collect(Collectors.toList());
        logger.info("Found {} root nodes for adminId {}: {}", roots.size(), adminId, roots);

        // 如果没有根节点,直接返回
        if (roots.isEmpty()) {
            AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
            result.setAdminId(adminId);
            result.setChildren(Collections.emptyList());
            return result;
        }

        // 为每个根节点构建树形结构
        List<InviteCodeTreeDTO> trees = new ArrayList<>();
        for (InviteCode root : roots) {
            InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, childrenMap, new HashSet<>());
            trees.add(tree);
        }

        AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
        result.setAdminId(adminId);
        result.setChildren(trees);
        return result;
    }

    private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap, Set<Integer> visited) {
        if (!visited.add(root.getId())) {
            throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());
        }

        InviteCodeTreeDTO node = new InviteCodeTreeDTO();
        node.setId(root.getId());
        node.setInviteCode(root.getInviteCode());
        node.setInviteLevel(root.getInviteLevel());
        node.setChildren(new ArrayList<>());

        // 直接从 childrenMap 获取子节点
        List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());
        logger.info("Found {} children for root node {}: {}", children.size(), root.getId(), children);

        // 递归构建子树
        for (InviteCode child : children) {
            logger.info("Building subtree for child node: {}", child.getId());
            InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap, new HashSet<>(visited));
            node.getChildren().add(childNode);
        }

        return node;
    }
}

🎉 总结

通过递归和 buildTree 方法,我们可以轻松地构建复杂的层级树形结构,为前端提供所需的树形数据。💻

  • 核心代码for (InviteCode child : children) { InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap); node.getChildren().add(childNode); } 递归构建子树。
  • 优势:代码简洁、通用性强、易于扩展。
  • 优化:添加循环检测、支持并行流、添加日志。

希望这篇博客对你理解递归和树形结构构建有所帮助!💬 如果你有其他问题,欢迎留言讨论!🚀

📚 参考:Java 官方文档、递归算法原理。点赞和分享哦!😊

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值