二叉树遍历-递归、迭代、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
}
相关推荐
WBluuue9 分钟前
Codeforces 1093 Div2(ABCD1D2)
c++·算法
浅念-17 分钟前
「一文吃透 BFS:从层序遍历到锯齿形、最大宽度、每层最大值」
数据结构·算法
汉克老师19 分钟前
GESP5级C++考试语法知识(十三、贪心算法(一))
算法·贪心算法·海盗船·gesp5级·gesp五级·排队接水
梦想画家1 小时前
Apache AGE实战指南:从Cypher语法到核心图算法
算法·cypher·apache age
刀法如飞2 小时前
Go数组去重的20种实现方式,AI时代解决问题的不同思路
后端·算法·go
旖-旎2 小时前
深搜练习(N皇后)(10)
c++·算法·深度优先·力扣
Controller-Inversion3 小时前
322. 零钱兑换
算法
头发够用的程序员3 小时前
C++和Python面试经典算法汇总(一)
开发语言·c++·python·算法·容器·面试
淡海水3 小时前
【AI模型】模型量化技术详解
人工智能·算法·机器学习
炸膛坦客3 小时前
嵌入式 - 数据结构与算法:(1-1)数据结构 - 顺序表(Sequential List)
数据结构·算法·嵌入式