LeetCode94.二叉树的中序遍历、LeetCode144. 二叉树的前序遍历、LeetCode145. 二叉树的后序遍历

二叉树的中序遍历

递归算法

1.代码实现

    def inorderTraversal(self, root):
        """
        二叉树中序遍历(递归实现)
        
        参数:
            root: 二叉树根节点
            
        返回:
            List[int]: 中序遍历结果列表
        """
        result = []
        
        def inorder(node):
            """递归辅助函数"""
            if not node:
                return
            
            # 遍历左子树
            inorder(node.left)
            
            # 访问根节点
            result.append(node.val)
            
            # 遍历右子树
            inorder(node.right)
        
        inorder(root)
        return result

2. 基本概念

  • 中序遍历 :按照"左子树 → 根节点 → 右子树"的顺序访问二叉树节点
  • 递归思想 :将问题分解为规模更小的同类问题,直到达到基本情况

3. 算法结构

def inorder(node):
    if not node:
        return
    
    # 1. 遍历左子树
    inorder(node.left)
    
    # 2. 访问根节点
    result.append(node.val)
    
    # 3. 遍历右子树
    inorder(node.right)

4. 关键要素

4.1 递归终止条件
if not node:
    return
  • 当节点为空时,直接返回,这是递归的终止条件
  • 防止无限递归,确保算法能够正常结束 3.2 递归调用顺序
  1. 先递归左子树 : inorder(node.left)

    • 先处理左子树的所有节点
    • 左子树处理完成后,才会执行后续代码
  2. 访问根节点 : result.append(node.val)

    • 将当前节点的值添加到结果列表中
    • 这是中序遍历的"中"部分
  3. 后递归右子树 : inorder(node.right)

    • 处理右子树的所有节点
    • 这是递归的最后一步

5. 算法特点

5.1 时间复杂度
  • O(n) :每个节点只被访问一次,n为节点数量
5.2 空间复杂度
  • O(h) :h为二叉树的高度
  • 递归调用栈的深度取决于树的高度
  • 最坏情况下(树退化为链表),空间复杂度为O(n)
5.3 递归调用栈
  • 每次递归调用都会在调用栈中创建一个新的栈帧
  • 栈帧中保存了当前节点的信息和返回地址
  • 递归返回时,从栈帧中恢复执行状态

6. 递归与迭代的对比

在这里插入图片描述
递归实现直接对应中序遍历的定义,代码简洁明了,但可能会因为递归深度过大导致栈溢出。在实际应用中,对于深度较大的树,可以考虑使用迭代实现。

7.实例演示递归序

用文字和表格展示完全二叉树 [4, 2, 6, 1, 3, 5, 7] 的递归序完整周期。

       4
     /   \
    2     6
   / \   / \
  1   3 5   7
7.1递归序完整周期分析

第一阶段:递归进入(沿左子树向下)
在这里插入图片描述
第二阶段:处理节点2及其右子树
在这里插入图片描述
第三阶段:处理根节点4及其右子树
在这里插入图片描述

7.2关键观察
  1. 节点访问顺序 :1 → 2 → 3 → 4 → 5 → 6 → 7(中序遍历结果)
  2. 最大递归深度 :3(树的高度)
  3. 最大栈大小 :3(节点4、2、1同时在栈中)
  4. 每个节点的处理模式 :
    • 调用(入栈)
    • 访问(处理节点值)
    • 返回(出栈)
  5. 递归序特点 :
    • 先沿左子树一直向下到最深处
    • 然后按"左-根-右"顺序访问节点
    • 最后逐层返回
7.3总结

这个完整周期展示了递归中序遍历的三个关键阶段:

  1. 递归进入 :从根节点开始,沿左子树一直向下
  2. 节点处理 :按中序顺序访问节点(左-根-右)
  3. 递归退出 :处理完节点后逐层返回
    通过这个表格,我们可以清晰地看到递归调用栈的动态变化,以及每个节点在何时被调用、访问和返回。

迭代算法

1.代码实现

    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        """  # 修正:缩进与函数内部代码一致(4个空格)
        二叉树中序遍历(迭代实现)
        
        参数:
            root: 二叉树根节点
            
        返回:
            List[int]: 中序遍历结果列表
        """
        result = []  # 缩进与文档字符串对齐,属于函数内部代码块
        stack = []
        current = root
        
        # 使用栈模拟递归过程:先遍历所有左子节点,再访问根节点,最后遍历右子节点
        while current or stack:
            # 1. 将当前节点及其所有左子节点入栈(先处理左)
            while current:
                stack.append(current)
                current = current.left
            
            # 2. 弹出栈顶节点,加入结果列表(处理中)
            current = stack.pop()
            result.append(current.val)
            
            # 3. 转向右子节点(处理右)
            current = current.right
        
        return result

2.算法原理

迭代实现使用 栈 来模拟递归过程,避免了递归调用带来的系统开销。中序遍历的顺序是"左-根-右",迭代实现的关键是:

  1. 先访问最左边的节点 :通过不断向左移动并将节点入栈
  2. 访问栈顶节点 :当到达最左边时,弹出栈顶节点并访问
  3. 转向右子树 :访问完当前节点后,转向其右子树继续遍历

3.执行步骤详解

以二叉树 [4, 2, 6, 1, 3, 5, 7] 为例:

       4
     /   \
    2     6
   / \   / \
  1   3 5   7
初始状态
- result = []
- stack = []
- current = root(4) 执行过程

在这里插入图片描述

4.关键点解析

  1. 外层循环条件 : while current or stack
    • 只要当前节点不为空或栈不为空,就继续遍历
    • 这确保了所有节点都能被访问到
  2. 内层循环 : while current
    • 将当前节点及其所有左子节点入栈
    • 直到到达最左边的叶子节点
  3. 节点访问 :
    • 弹出栈顶节点(最左边的未访问节点)
    • 访问该节点(将其值加入结果列表)
    • 转向其右子树继续遍历

5.优势与适用场景

  1. 优势 :
    • 避免了递归深度过大导致的栈溢出
    • 没有函数调用的额外开销
    • 可以更好地控制内存使用
  2. 适用场景 :
    • 树的深度可能很大,递归可能导致栈溢出
    • 对性能要求较高的场景
    • 需要精确控制内存使用的环境
      这种迭代实现是二叉树遍历的经典算法,通过栈结构巧妙地模拟了递归过程,实现了与递归相同的效果,但避免了递归的潜在问题。

递归实现 vs 迭代实现的核心区别

递归实现中的栈操作时机

在递归实现中:

  • 调用(入栈) :执行 inorder(node) 时立即入栈
  • 访问节点 :执行 result.append(node.val) 时,节点 仍然在栈中
  • 返回(出栈) :整个函数执行完毕(包括左子树、访问节点、右子树)后才出栈
    因此,在递归序的步骤6中:
  • 操作类型是 访问 节点1
  • 此时节点1 尚未返回 ,所以仍然在调用栈中
  • 调用栈状态保持为 [4,2,1]
  • 直到步骤9执行 返回 操作时,节点1才会出栈,栈状态变为 [4,2]

迭代实现中的栈操作时机

在迭代实现中:

  • 入栈 :主动将节点压入栈中
  • 出栈 :当需要访问节点时,先将节点弹出栈
  • 访问节点 :在弹出栈之后执行
    所以在迭代实现中,访问节点1的步骤会同时执行出栈操作,栈状态会立即变化。

为什么会有这种差异?

这是因为:

  • 递归实现 :调用栈由系统自动管理,函数执行过程中节点始终在栈中,直到完全执行完毕
  • 递归序 :描述的是递归函数的逻辑执行顺序,区分了"访问节点"和"返回"两个不同阶段
  • 迭代实现 :我们手动管理栈,需要将"出栈"和"访问"合并为一个操作步骤

二叉树的前序遍历

递归代码实现

    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        """
        二叉树的前序遍历
        :param root: 二叉树的根节点
        :return: 前序遍历的节点值列表
        """
        # 方法一:递归实现
        result = []
        
        def traverse(node):
            if not node:
                return
            # 前序遍历:根节点 -> 左子树 -> 右子树
            result.append(node.val)
            traverse(node.left)
            traverse(node.right)
        
        traverse(root)
        return result

迭代代码实现

    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        """
        二叉树的前序遍历
        :param root: 二叉树的根节点
        :return: 前序遍历的节点值列表
        """       
        # 方法二:迭代实现
        if not root:
            return []
        
        result = []
        stack = [root]  # 使用栈来模拟递归过程
        
        while stack:
            node = stack.pop()  # 弹出栈顶节点
            result.append(node.val)  # 访问根节点
            
            # 注意:栈是先进后出,所以先压入右子节点,再压入左子节点
            # 这样弹出时会先处理左子节点,符合前序遍历顺序
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        
        return result

二叉树的后序遍历

递归代码实现

    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        """
        二叉树的后序遍历
        :param root: 二叉树的根节点
        :return: 后序遍历的节点值列表
        """
        # 方法一:递归实现
        result = []

        def traverse(node):
            if not node:
                return
            # 后序遍历:左子树 -> 右子树 -> 根节点
            traverse(node.left)
            traverse(node.right)
            result.append(node.val)

        traverse(root)
        return result

迭代代码实现

    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        """
        二叉树的后序遍历
        :param root: 二叉树的根节点
        :return: 后序遍历的节点值列表
        """      
        # 方法二:迭代实现
        """
        迭代实现后序遍历的核心思想:
        1. 使用栈来模拟递归过程
        2. 后序遍历顺序:左子树 -> 右子树 -> 根节点
        3. 技巧:先按照根节点 -> 右子树 -> 左子树的顺序遍历,然后反转结果
        4. 这样可以复用前序遍历的思路,只是调整子节点的入栈顺序
        """
        if not root:
            return []
        
        result = []
        stack = [root]  # 初始化栈,将根节点压入栈中开始遍历
        
        while stack:  # 栈不为空时继续遍历
            node = stack.pop()  # 弹出栈顶节点
            result.append(node.val)  # 访问根节点
            
            # 注意:栈是先进后出,我们希望最终结果是左子树 -> 右子树 -> 根节点
            # 所以先压入左子节点,再压入右子节点
            # 这样弹出时会先处理右子节点,得到根->右->左的顺序
            if node.left:  # 如果有左子节点,先压入栈
                stack.append(node.left)
            if node.right:  # 如果有右子节点,后压入栈
                stack.append(node.right)
        
        # 反转结果得到后序遍历顺序:左 -> 右 -> 根
        return result[::-1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lcreek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值