二叉树遍历-递归、迭代、Morris

其实就后序稍微麻烦一些,因为考虑到 "左右中"的话,需要记录是否把左右节点都访问了,其实就是解决 需要两次回到"中"的问题

递归

复制代码
package main

import "fmt"

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

// ==================== 递归遍历 ====================

// PreorderRecursive 先序遍历(根左右)
func PreorderRecursive(root *TreeNode, result *[]int) {
	if root == nil {
		return
	}
	*result = append(*result, root.Val)
	PreorderRecursive(root.Left, result)
	PreorderRecursive(root.Right, result)
}

// InorderRecursive 中序遍历(左根右)
func InorderRecursive(root *TreeNode, result *[]int) {
	if root == nil {
		return
	}
	InorderRecursive(root.Left, result)
	*result = append(*result, root.Val)
	InorderRecursive(root.Right, result)
}

// PostorderRecursive 后序遍历(左右根)
func PostorderRecursive(root *TreeNode, result *[]int) {
	if root == nil {
		return
	}
	PostorderRecursive(root.Left, result)
	PostorderRecursive(root.Right, result)
	*result = append(*result, root.Val)
}

迭代(栈)

复制代码
// PreorderIterative 先序遍历(使用栈)
func PreorderIterative(root *TreeNode) []int {
	if root == nil {
		return []int{}
	}
	result := []int{}
	stack := []*TreeNode{root}

	for len(stack) > 0 {
		node := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		result = append(result, node.Val)

		// 先右后左,保证左子树先出栈
		if node.Right != nil {
			stack = append(stack, node.Right)
		}
		if node.Left != nil {
			stack = append(stack, node.Left)
		}
	}
	return result
}

// InorderIterative 中序遍历(使用栈)
func InorderIterative(root *TreeNode) []int {
	result := []int{}
	stack := []*TreeNode{}
	cur := root

	for cur != nil || len(stack) > 0 {
		// 将左子树全部入栈
		for cur != nil {
			stack = append(stack, cur)
			cur = cur.Left
		}
		// 弹出栈顶并访问
		cur = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		result = append(result, cur.Val)
		// 转向右子树
		cur = cur.Right
	}
	return result
}

// PostorderIterative 后序遍历(双栈法)
func PostorderIterative(root *TreeNode) []int {
	if root == nil {
		return []int{}
	}
	result := []int{}
	stack1 := []*TreeNode{root}
	stack2 := []*TreeNode{}

	// stack1 用于先序遍历(根右左),结果压入 stack2
	for len(stack1) > 0 {
		node := stack1[len(stack1)-1]
		stack1 = stack1[:len(stack1)-1]
		stack2 = append(stack2, node)

		if node.Left != nil {
			stack1 = append(stack1, node.Left)
		}
		if node.Right != nil {
			stack1 = append(stack1, node.Right)
		}
	}

	// 弹出 stack2 即为后序(左右根)
	for i := len(stack2) - 1; i >= 0; i-- {
		result = append(result, stack2[i].Val)
	}
	return result
}

关于后序遍历的迭代,其实还可以记录节点是否已经访问过右子树

复制代码
func PostorderIterative(root *TreeNode) []int {
    stack := []*TreeNode{root}
    // 需要额外记录节点是否已访问过右子树
    visited := make(map[*TreeNode]bool)  // 或使用双栈
    
    for len(stack) > 0 {
        node := stack[len(stack)-1]
        if node == nil {
            stack = stack[:len(stack)-1]
            continue
        }
        
        if !visited[node] {
            // 第一次遇到:标记,压入右、左
            visited[node] = true
            stack = append(stack, node.Right, node.Left)
        } else {
            // 第二次遇到(左右都处理完):访问
            stack = stack[:len(stack)-1]
            result = append(result, node.Val)
        }
    }
}

Morris

先序

复制代码
// PreorderMorris Morris先序遍历
func PreorderMorris(root *TreeNode) []int {
    result := []int{}
    cur := root
    
    for cur != nil {
        if cur.Left == nil {
            // 没有左孩子,访问当前节点,然后向右
            result = append(result, cur.Val)
            cur = cur.Right
        } else {
            // 寻找前驱
            pre := cur.Left
            for pre.Right != nil && pre.Right != cur {
                pre = pre.Right
            }
            
            if pre.Right == nil {
                // 第一次到达:访问当前节点,建立线索,然后向左
                result = append(result, cur.Val) // <--- 和中序唯一的区别:访问时机提前
                pre.Right = cur
                cur = cur.Left
            } else {
                // 第二次到达:删除线索,然后向右
                pre.Right = nil
                cur = cur.Right
            }
        }
    }
    return result
}

中序

复制代码
// InorderMorris Morris中序遍历,O(1)空间
func InorderMorris(root *TreeNode) []int {
    result := []int{}
    cur := root
    
    for cur != nil {
        // 情况1: 没有左子树,直接处理当前节点,然后向右
        if cur.Left == nil {
            result = append(result, cur.Val)
            cur = cur.Right
            continue
        }
        
        // 情况2: 有左子树,找到前驱节点 (左子树的最右节点)
        pre := cur.Left
        // 找最右节点,且不能是已经建立过线索的节点 (pre.Right != cur)
        for pre.Right != nil && pre.Right != cur {
            pre = pre.Right
        }
        
        // 判断前驱节点的右指针状态
        if pre.Right == nil {
            // 第一次到达 cur:建立线索,然后深入左子树
            pre.Right = cur
            cur = cur.Left
        } else { 
            // pre.Right == cur:第二次到达,说明左子树已处理完
            // 删除线索,访问当前节点,然后转向右子树
            pre.Right = nil
            result = append(result, cur.Val)
            cur = cur.Right
        }
    }
    return result
}

后序

后序多说一句,是反向先序,做一下翻转

复制代码
// PostorderMorris Morris后序遍历
// 策略:先以"根-右-左"的顺序遍历,再反转结果
func PostorderMorris(root *TreeNode) []int {
    if root == nil {
        return []int{}
    }
    
    // 第一步:按"根-右-左"顺序遍历(Morris版)
    result := make([]int, 0)
    cur := root
    
    for cur != nil {
        if cur.Right == nil {
            // 没有右孩子,访问当前节点,然后向左
            result = append(result, cur.Val)
            cur = cur.Left
        } else {
            // 寻找后继节点(右子树的最左节点)
            succ := cur.Right
            for succ.Left != nil && succ.Left != cur {
                succ = succ.Left
            }
            
            if succ.Left == nil {
                // 第一次到达:访问当前节点,建立线索,然后向右
                result = append(result, cur.Val)
                succ.Left = cur
                cur = cur.Right
            } else {
                // 第二次到达:删除线索,然后向左
                succ.Left = nil
                cur = cur.Left
            }
        }
    }
    
    // 第二步:反转结果
    for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
        result[i], result[j] = result[j], result[i]
    }
    
    return result
}
相关推荐
6Hzlia8 小时前
【Classic 150 刷题计划】 LeetCode 228. 汇总区间 | C++ 双游标区间扫描与 to_string 规范
c++·算法·leetcode
玖玥拾8 小时前
LeetCode 383 赎金信
算法·leetcode
维克兜率天8 小时前
5.3 宏观因子:抬头看天
笔记·python·深度学习·算法·量化
LuminousCPP8 小时前
数据结构-排序(五):计数排序与排序专题阶段总结|从外部归并到线性排序,再看算法边界与选型
c语言·数据结构·笔记·算法·排序算法
流云枫8 小时前
003-上下文窗口:物理限制、KV Cache 与工程应对策略
算法
盟道科技9 小时前
小程序订阅消息大规模投递实践:频控令牌池、Redis限流与失败补偿设计
分布式·算法·小程序
工具派9 小时前
视频场景检测是怎么找到切镜头位置的?三种思路与实测
算法·计算机视觉·视频
钓鱼的肝9 小时前
csp-j-s总结(1)
c++·笔记·算法·青少年编程
流云枫9 小时前
001-Transformer架构与Self-Attention机制深度解析
算法
生信大杂烩9 小时前
Xenium H&E空间原位可视化——细胞轮廓、基因表达与转录本可视化
python·算法·数据分析