树形结构构建的两种方式

树形结构构建的两种方式

树形结构(Tree Structure)是一种常用的数据结构,用于表示具有层次关系的数据集。树形结构由节点(Nodes)组成,这些节点通过边(Edges)相互连接。每个节点可以有零个或多个子节点,但只有一个父节点(除了根节点外)。

一,树形结构的基本概念

  1. 节点(Node)
    • 每个树的组成部分,可以包含数据和其他信息。
    • 每个节点可以有零个或多个子节点。
  2. 根节点(Root Node)
    • 树的顶部节点,没有父节点。
    • 整棵树的起点。
  3. 子节点(Child Node)
    • 每个节点可以拥有一个或多个子节点。
    • 子节点是从父节点延伸出来的。
  4. 父节点(Parent Node)
    • 每个节点(除了根节点)都有一个直接的父节点。
    • 父节点是子节点的直接上级节点。
  5. 叶子节点(Leaf Node)
    • 没有子节点的节点。
    • 位于树的最底层。
  6. 路径(Path)
    • 从一个节点到另一个节点的一系列连续的边。
  7. 深度(Depth)
    • 一个节点到根节点的路径长度。
    • 根节点的深度为 0。
  8. 高度(Height)
    • 树的高度是指从根节点到最远叶子节点的最长路径长度。
    • 树的高度至少为 0(仅包含根节点的树)。

二,使用场景

这种树形结构在多种应用场景中都非常有用,例如:

  1. 行政区域划分
    • 将省份、城市、区县等组织成树形结构,便于管理和查询。
  2. 组织结构
    • 公司内部的部门和职位关系,可以表示为树形结构。
  3. 文件系统
    • 计算机文件系统的目录结构就是一个典型的树形结构。
  4. 产品分类
    • 电子商务网站中的商品分类通常也是树形结构。
  5. 权限管理
    • 用户权限和角色管理中,权限和角色之间的关系可以用树形结构表示。
  6. 菜单导航
    • 网站或应用程序中的多级菜单结构。

三,简单树形结构的创建方式

这里我使用省市县为代表来进行代码实现

1.递归

——(不太推荐)

递归的思路是从顶级节点开始,不断寻找其子节点,并将这些子节点递归地构建到对应的父节点下。

以下是递归版本的代码实现:

@GetMapping("/cityTree") // 将该方法映射为 HTTP GET 请求,访问路径为 /cityTree
public List<City> cityTree() {

    // 从 cityService 中获取所有城市的列表
    List<City> list = cityService.list();

    // 创建一个 HashMap,用于快速查找城市对象
    HashMap<Integer, City> baseMap = new HashMap<>();

    // 将所有城市对象按 ID 存入 baseMap
    for (City city : list) {
        baseMap.put(city.getId(), city);
    }

    // 返回顶级城市列表,通过递归的方式构建子节点
    return buildCityTree(0, baseMap);
}

/**
 * 递归构建城市树形结构
 * 
 * @param parentId 父节点 ID
 * @param baseMap 存储所有城市对象的 Map
 * @return 父节点的子城市列表
 */
private List<City> buildCityTree(int parentId, HashMap<Integer, City> baseMap) {
    List<City> children = new ArrayList<>();

    // 遍历 baseMap 中的所有城市对象
    for (City city : baseMap.values()) {
        // 如果当前城市的父 ID 与传入的 parentId 匹配,则将其视为当前节点的子节点
        if (city.getParentId() == parentId) {
            // 递归构建当前城市的子节点
            city.setChildren(buildCityTree(city.getId(), baseMap));
            // 将当前城市添加到父节点的子城市列表中
            children.add(city);
        }
    }

    return children; // 返回当前节点的子节点列表
}
递归的优缺点

优点:

  1. 代码简洁易读:递归的代码通常更简洁和易于理解,符合人的逻辑思维,直接表达出层级结构的构建过程。
  2. 模块化强:递归将问题分解成子问题,天然符合模块化的设计思想,便于维护和扩展。
  3. 符合层次遍历的自然方式:递归非常适合树形和层次结构的数据处理,代码中直接展现了父子关系的递进性。

缺点:

  1. 内存开销较大:递归调用会占用系统的栈空间,当数据量大或者层级深度过大时,可能导致栈溢出(StackOverflowError)。
  2. 性能问题:递归涉及多次函数调用,调用栈的创建和销毁会带来一定的性能开销,尤其是在大量数据或深度递归时,性能可能不如迭代方式。
  3. 调试困难:递归程序的执行顺序较复杂,调试时不容易跟踪每次调用的状态和变化,容易出错。

递归适用于层次结构明显、数据规模适中的场景。当数据规模较大时,需小心栈空间的限制,可能需要优化或使用其他方式(如显式栈的迭代法)来替代递归。

2.借助Map循环构建

@GetMapping("/cityTree")  // 将该方法映射为 HTTP GET 请求,访问路径为 /cityTree
public List<City> cityTree() {

    // 从 cityService 中获取所有城市的列表
    List<City> list = cityService.list();

    // 创建一个 HashMap,用于存储城市对象,以城市的 ID 作为键,城市对象作为值
    HashMap<Integer, City> baseMap = new HashMap<>();
    // 遍历所有城市,将城市对象存入 baseMap 中
    for (City city : list) {
        baseMap.put(city.getId(), city);
    }

    // 创建一个 List,用于存储最终的城市树形结构
    List<City> cityList = new ArrayList<>();

    // 再次遍历城市列表,将城市按照层级关系组装为树形结构
    for (City city : list) {
        // 判断是否为顶级父类(根节点),即 parentId 为 0
        if (city.getParentId() == 0) {
            cityList.add(city);  // 将顶级城市添加到 cityList 中
            continue;  // 继续下一次循环
        }

        // 如果不是顶级城市,则将其作为子节点添加到对应的父节点下
        City parent = baseMap.get(city.getParentId());  // 获取当前城市的父城市对象
        List<City> children = parent.getChildren();  // 获取父城市的子城市列表

        // 如果子城市列表为空,则初始化子城市列表
        if (children == null) {
            children = new ArrayList<>();
            parent.setChildren(children);  // 将初始化的子城市列表设置回父城市对象中
        }

        // 将当前城市添加到父城市的子城市列表中
        children.add(city);
    }

    // 返回组装好的树形城市列表
    return cityList;
}
功能说明

该方法的主要功能是将一组城市数据根据父子关系组装成树形结构,并返回顶层的城市列表。树形结构意味着每个城市对象可能包含一个子城市列表,这样的层次结构反映了城市间的层级关系(比如省市区的关系)。

实现原理
  1. 数据获取与初始化:首先通过 cityService.list() 获取所有城市的列表,并将所有城市放入 baseMap 中,便于快速通过 ID 查找城市对象。
  2. 城市关系构建:遍历城市列表,根据每个城市的 parentId 判断其是否为顶级节点。如果是顶级节点(parentId == 0),直接添加到 cityList 中;否则,将其作为子节点添加到对应父节点的 children 列表中。
  3. 结果返回:最终返回包含树形结构的顶级城市列表 cityList
优缺点
优点:
  • 高效的父子关系查找:通过 HashMap 快速查找父节点,时间复杂度为 O(1),大大提高了构建树形结构的效率。
  • 结构清晰:代码逻辑分明,容易理解,尤其适合树形结构的构建和展示。
缺点:
  • 内存占用较大:需要额外的 HashMap 来存储城市数据,增加了一定的内存开销。
  • 数据完整性依赖外部数据源:假设 cityService.list() 返回的数据是完整且无误的,未考虑数据异常或缺失的情况,比如缺少某些父节点。
  • 无法处理循环依赖:如果城市数据存在循环依赖(如子节点指向了自己的父节点),可能导致逻辑错误或无限循环,需额外校验数据的合法性。

综合对比

比较维度借助Map循环构建方法递归构建方法
代码简洁度逻辑较多,代码稍显复杂代码简洁,符合自然层次结构
性能较高效,无递归栈调用开销存在递归栈调用开销,较大数据量时性能下降
内存使用需要额外的 HashMap,占用较多内存占用内存较少,但深度过大会消耗栈空间
可维护性逻辑清晰,容易理解和调试递归过程复杂,调试和错误处理较难
安全性无栈溢出风险,适合深层次复杂结构存在栈溢出风险,不适合深度递归

总结

  • 选择递归:如果数据层次结构清晰且层次较少,递归的实现会更简洁直观,易于维护。
  • 选择循环:当面对大数据量和复杂层次的情况时,循环构建方法更为稳定和高效,避免了递归的局限性。

两种方法各有优缺点,选择时应根据实际数据规模、层次深度和对性能的需求综合考虑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值