其实就后序稍微麻烦一些,因为考虑到 "左右中"的话,需要记录是否把左右节点都访问了,其实就是解决 需要两次回到"中"的问题
递归
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
}