导读:本期聚焦于越南程序员创作的《如何通过递归实现二叉树的按层遍历并打印指定层级的节点》,敬请观看详情。二叉树的层序遍历通常借助队列来完成,但如果题目要求只能用递归实现,或者需要直接打印某一层的节点,该怎么办?本文从递归的核心思路入手,讲解如何通过记录当前递归深度来模拟层级信息,并利用深度优先搜索访问每一个节点,当深度等于目标层级时输出节点值。文章同时给出完整的代码实现,分析递归解法与队列解法在时间和空间上的差异,还总结了处理空节点、边界层级等常见易错点,帮助你在面试和刷题时快速写出正确的递归版按层遍历。

提到二叉树的按层遍历,大多数人第一反应是用队列做广度优先搜索。不过在一些面试场景或者算法题里,经常会出现附加限制条件,比如不允许使用队列、要求用递归实现,或者更直接一点:给定一个层级 k,把这个层级上的所有节点打印出来。这时候就需要换一种思路,用深度优先的方式去模拟按层访问的效果。

如何通过递归实现二叉树的按层遍历并打印指定层级的节点

递归实现按层遍历的核心思路

队列解法的本质是:节点出队的顺序天然就是按层排列的。而递归解法的本质则是换一个角度——我们仍然按照深度优先的顺序访问节点(先一路走到底,再回溯),但在递归函数里多带一个参数depth,用来记录当前节点位于第几层。

这样一来,虽然访问顺序不是按层的,但每个节点都知道自己在哪一层。如果我们想收集第 k 层的所有节点,只需要在访问每个节点时判断一下:当depth == k时,把节点值加入结果即可。配合二叉树先左后右的遍历顺序,同一层内的节点依然会按照从左到右的顺序被收集,这正好符合按层打印的要求。

举个具体例子,对于一棵简单的树:根节点为 1,第二层是 2 和 3,第三层是 4、5、6。递归从根节点开始,depth 为 1;走到节点 2 时 depth 变成 2,走到节点 4 时 depth 变成 3,依此类推。每进入一层,depth 加一;每回溯一层,层级状态由调用栈自然负责恢复,这正是递归写法简洁的地方。

完整代码实现:打印指定层级的节点

下面用 Go 语言实现一个函数,输入二叉树的根节点和目标层级 k,返回第 k 层的所有节点值(k 从 1 开始计数)。如果目标层级超过树的高度,返回空切片。

package main

import "fmt"

// TreeNode 定义二叉树节点结构
type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

// collectByLevel 递归收集第 target 层的节点值
// node 为当前节点,depth 为当前层级(从 1 开始),target 为目标层级
func collectByLevel(node *TreeNode, depth, target int, result *[]int) {
    // 空节点直接返回,什么都不做
    if node == nil {
        return
    }
    // 当前层级等于目标层级,收集该节点
    if depth == target {
        *result = append(*result, node.Val)
        // 目标层以下不会再有同层节点,提前返回做剪枝
        return
    }
    // 否则继续向左右子树递归,层级加一
    collectByLevel(node.Left, depth+1, target, result)
    collectByLevel(node.Right, depth+1, target, result)
}

// PrintLevel 对外暴露的入口函数
func PrintLevel(root *TreeNode, k int) []int {
    result := make([]int, 0)
    collectByLevel(root, 1, k, &result)
    return result
}

func main() {
    // 构造测试用例树:        1
    //                       / \
    //                      2   3
    //                     / \   \
    //                    4   5   6
    root := &TreeNode{Val: 1,
        Left: &TreeNode{Val: 2,
            Left:  &TreeNode{Val: 4},
            Right: &TreeNode{Val: 5},
        },
        Right: &TreeNode{Val: 3,
            Right: &TreeNode{Val: 6},
        },
    }

    fmt.Println(PrintLevel(root, 2)) // 输出: [2 3]
    fmt.Println(PrintLevel(root, 3)) // 输出: [4 5 6]
}

这段代码有几个值得注意的细节。第一,结果切片用指针*[]int传入,避免每次递归调用都产生切片头拷贝的歧义,也可以改用包级变量或者闭包捕获,效果相同。第二,当depth == target时直接return,这是一个小的剪枝优化——既然目标层已经到达,再往下走不可能出现同层节点,提前返回可以减少无用的递归调用。

第三点是边界处理:PrintLevel(root, 0)或者传入超过树高的层级时,函数会返回空切片而不是报错,因为递归永远满足不了depth == target的条件。这种安静地返回空结果的行为在大多数场景下是合理的,但如果你希望显式区分非法输入,可以在入口处先校验 k 是否大于等于 1。

从打印单层扩展到完整的按层遍历

只打印一层还不够,很多时候题目要求把整棵树按层输出成一个二维数组,也就是类似经典算法题的形式。用递归同样可以做到:最直接的办法是先遍历一次求出树的高度 h,然后对 1 到 h 的每一层分别调用上面的PrintLevel函数。这种写法最容易理解,但每调用一次都要完整遍历一遍树,总时间复杂度是 O(n·h),在树很高时效率不理想。

更优雅的做法是只递归一次,把所有层的结果同时收集齐。思路是维护一个二维切片levels,递归时根据 depth 判断该层的结果切片是否已经初始化,没有则追加一个新的空切片,然后把当前节点值追加到对应层的切片末尾:

// LevelOrder 递归实现完整的层序遍历
func LevelOrder(root *TreeNode) [][]int {
    levels := make([][]int, 0)
    var dfs func(node *TreeNode, depth int)
    dfs = func(node *TreeNode, depth int) {
        if node == nil {
            return
        }
        // 第一次到达某一层时,为该层创建结果切片
        if depth == len(levels) {
            levels = append(levels, make([]int, 0))
        }
        // 将当前节点值放入对应层
        levels[depth] = append(levels[depth], node.Val)
        // 递归处理左右子树
        dfs(node.Left, depth+1)
        dfs(node.Right, depth+1)
    }
    dfs(root, 0)
    return levels
}

这个版本的时间复杂度是 O(n),每个节点只被访问一次。depth == len(levels)这个判断很关键:由于总是先访问左子树,某一层第一次被触达时对应的必然是该层最左边的节点,此时len(levels)正好等于 depth,我们就为这一层开辟存储空间;后续同层的节点会因为 depth 小于 len(levels) 而直接走追加逻辑,不会重复建层。

递归解法与队列解法的对比及常见坑

从复杂度上看,两种解法的时间复杂度都是 O(n),差别主要在空间上。队列解法的空间取决于树的最大宽度,最坏情况(满二叉树的最底层)大约有 n/2 个节点同时排队;递归解法的空间取决于树的高度,最坏情况(链状退化树)需要 n 层调用栈。所以对于很宽但很矮的树,递归反而更省内存;对于极不平衡的树,队列解法更稳妥。另外递归深度过大时要注意栈溢出风险,Go 语言中 goroutine 栈可以动态扩容问题不大,但在栈大小固定的语言里需要特别留意。

实现过程中有几个容易踩的坑需要提醒。一是层级计数从 0 还是 1 开始必须全局统一,入口函数传 1 内部判断就要用 1,前后不一致会导致结果整体偏移一层,这是新手最常犯的错误。二是空节点判断必须放在递归函数的最开头,否则对 nil 节点取node.Val会直接 panic。三是如果题目要求之字形打印(奇数层正序、偶数层倒序),只需在收集完某一层后根据层的奇偶性做一次反转,递归框架完全不用改动。

总的来说,递归实现按层遍历的关键就一句话:给递归函数加上深度参数,让每个节点携带自己的层级信息。理解了这一点,无论是打印指定层、收集所有层,还是变体的之字形遍历,都只是在这个骨架上做少量修改而已。建议自己动手把上面的代码敲一遍,再和队列版本对比着实现一次,对这个知识点的掌握会扎实很多。

二叉树递归遍历层序遍历修改时间:2026-09-10 05:04:47

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0910/53827.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。