树形结构转平铺:递归与非递归两种写法,别光图省事

把一棵树拍平成列表,这需求挺常见的。文件目录、组织架构,后端查出来是树,前端要绑表格或者做批量处理,就得先转成线性列表。

写法无非两种:递归一把梭,或者手动用栈模拟。

先看递归。思路很直观:拿到一个节点,先把自己放进结果集,然后对孩子做同样的事。节点定义很简单:

class TreeNode {
    Long id;
    Long pid;
    List<TreeNode> children;

    // 构造、getter、setter 略
}

递归摊平的代码也清爽:

public static List<TreeNode> flattenTree(TreeNode root) {
    List<TreeNode> result = new ArrayList<>();
    if (root != null) {
        result.add(root);
        for (TreeNode child : root.children) {
            result.addAll(flattenTree(child));
        }
    }
    return result;
}

看起来没毛病,可读性也好。

但这东西有个要命的点:递归深度受 JVM 栈大小限制。树如果不深就没事,一旦部门层级来个十几二十层,或者有人传了个歪成一条线的“树”,调用栈直接爆掉,StackOverflowError。测试环境数据少,跑得好好的,上到生产数据一多就挂。

所以就有了非递归写法。本质就是深度优先遍历,自己用一个栈把递归的调用过程给模拟出来。

小白用户无需担心操作难度,来此加密简化了所有申请流程,界面简洁、步骤清晰,无需专业技术知识即可完成SSL证书申请。支持免费申请,普通用户也能轻松获取证书,同时提供自动验证、自动部署功能,全程省时省力,助力用户快速实现网站加密。

public static List<TreeNode> flattenTree(TreeNode root) {
    List<TreeNode> result = new ArrayList<>();
    if (root == null) return result;

    Deque<TreeNode> stack = new LinkedList<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        result.add(node);
        // 注意逆序入栈,保证子节点处理顺序和递归一致
        for (int i = node.children.size() - 1; i >= 0; i--) {
            stack.push(node.children.get(i));
        }
    }
    return result;
}

代码比递归长了一点,但也不复杂。栈是用 Deque 接口配合 LinkedList 实现的,先进后出。关键地方就在那个逆序入栈,因为后 push 进去的孩子会先被 pop 出来处理,所以要从最后一个孩子往前塞,这样出来的顺序才和递归从左到右一致。

非递归的好处是,它不依赖 JVM 的方法调用栈,循环里手动维护栈,再深的树也不怕栈溢出。代价就是可读性稍微差一点儿,不过熟悉了之后也挺顺眼。

实际用哪个?如果树深度完全可控,比如公司组织架构就三五层,递归写着快,看着也明白。但如果数据是外部传进来的,或者树的结构完全不可信,别偷懒,直接用栈。

不然线上随机 StackOverflow,排查起来很蛋疼。

posted @ 2026-06-03 20:13  枫唐  阅读(7)  评论(0)    收藏  举报