二叉树遍历-递归、迭代、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
}
相关推荐
小羊在睡觉2 小时前
力扣84. 柱状图中最大的矩形
后端·算法·leetcode·golang·go
3DVisionary2 小时前
蓝光三维扫描:医疗制造的精度焦虑怎么解
人工智能·算法·制造·蓝光三维扫描·医疗制造·三维检测·义齿检测
好评笔记2 小时前
机器学习面试八股——常用损失函数
人工智能·深度学习·算法·机器学习·校招
weixin_468466852 小时前
全局与局部注意力机制新手实战指南
人工智能·python·深度学习·算法·自然语言处理·transformer·注意力机制
_日拱一卒3 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
珂朵莉MM3 小时前
第七届全球校园人工智能算法精英大赛-算法巅峰赛产业命题赛第3赛季优化题--束搜索
人工智能·算法
Omics Pro4 小时前
首个!外源天然产物综合性代谢图谱
数据库·人工智能·算法·机器学习·r语言
voidmort4 小时前
3. 微调(Fine-tuning)与强化学习(RL)的核心思想
python·深度学习·算法
人道领域5 小时前
【LeetCode刷题日记】669.修剪二叉搜索树
开发语言·python·算法
QiLinkOS5 小时前
【从实验室到商业战场:发明专利如何重塑科技与企业的共生生态】
大数据·c语言·数据结构·c++·人工智能·单片机·算法