二叉树遍历-递归、迭代、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
}
相关推荐
sel_917 分钟前
【强化学习】Hands-on Modern RL项目实践|OPD 算法完整解析
人工智能·深度学习·算法·机器学习·语言模型
Navigator_Z1 小时前
LeetCode //C - 1209. Remove All Adjacent Duplicates in String II
c语言·算法·leetcode
ShineWinsu3 小时前
对于 C++:C++14中从变量模板、泛型 Lambda 到并发与字面量的解析
c++·算法
土司大王3 小时前
LeetCode hot100——合并两个有序链表
算法·leetcode·链表
码行山野赴时序归途3 小时前
算法效率的度量(上):时间复杂度中的对数思维
c语言·数据结构·算法
算AI4 小时前
基于LLM的无人机仿真测试:新方法竞赛夺佳绩
人工智能·深度学习·算法·机器学习·ai
kaixin_啊啊5 小时前
专用优化算法LKH
算法
_Narcissus_7 小时前
二分算法笔记及例题
数据结构·c++·笔记·算法·蓝桥杯·查找·二分算法
tachibana27 小时前
RAGAS 指标解读
数据库·人工智能·算法·机器学习·架构·大模型·llm