二叉树遍历-递归、迭代、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
}
相关推荐
碧海银沙音频科技研究院4 小时前
虚拟机ubuntu与windows共享文件夹(Samba共享)解决WSL加载SI工程满卡问题
人工智能·深度学习·算法
CoovallyAIHub4 小时前
ICLR 2026 | VLM自己学会调检测器:VTool-R1用强化学习教视觉模型使用工具推理
算法·架构·github
CoovallyAIHub4 小时前
RK3588上111 FPS:轻量YOLOv8+异步视频处理系统实现无人机自主电力巡检
算法·架构·github
炽烈小老头5 小时前
【每天学习一点算法 2026/04/13】两数相除
学习·算法
嘻嘻哈哈樱桃5 小时前
俄罗斯套娃信封问题力扣--354
算法·leetcode·职场和发展
田梓燊5 小时前
2026/4/12 leetcode 1320
算法·leetcode·职场和发展
j_xxx404_5 小时前
力扣题型--链表(两数相加|两两交换链表中的节点|重排链表)
数据结构·c++·算法·leetcode·蓝桥杯·排序算法
AI科技星5 小时前
v=c 物理理论核心参数转换表达式大全
开发语言·线性代数·算法·数学建模·平面
WolfGang0073216 小时前
代码随想录算法训练营 Day33 | 动态规划 part06
算法·leetcode·动态规划