二叉树遍历

65 阅读3分钟

层序遍历

  • Go中list包的链表是一个双向链表,可用来作为一个先进先出的队列

  • 广度优先遍(BFS)历通常借助队列实现

  • 层序遍历代码

func levelOrder(root *TreeNode) [][]int {
    if root == nil {
        return [][]int{}
    }
    res := [][]int{}
    queue := list.New() // 双向链表
    queue.PushBack(root)
    for queue.Len() > 0 {
        len := queue.Len()
        temp := []int{}
        for i := 0; i < len; i++ {
            // 类型断言,用于将 interface{} 类型的值转换为具体的类型 *TreeNode
            node := queue.Remove(queue.Front()).(*TreeNode)
            temp = append(temp, node.Val)
            if node.Left != nil {
                queue.PushBack(node.Left)
            }
            if node.Right != nil {
                queue.PushBack(node.Right)
            }
        }
        res = append(res, temp)
    }
    return res
}
  • T(n) = O(n), S(n)最差 = O(n)

力扣相关题目:

102. 二叉树的层序遍历

前、中、后序遍历的递归实现

  • 深度优先搜索(DFS)通常基于递归实现

  • 递归的关键是:找到最后return的条件,并写在递归函数的开头

  • 前序遍历

func preorderTraversal(root *TreeNode) []int {
    res := []int{}
    var preFunc func(root *TreeNode)
    preFunc = func(root *TreeNode) {
        if root == nil {
            return
        }
        // 根左右
        res = append(res, root.Val)
        preFunc(root.Left)
        preFunc(root.Right)
    }
    preFunc(root)
    return res
}
  • T(n) = O(n), S(n)最差 = O(n)

前、后序遍历的非递归实现(迭代法实现)

  • 迭代法遍历二叉树实际上就是模拟一个栈进行操作,Go中用切片可以模拟栈操作

  • 前序遍历

    • 前序遍历为例(根左右),栈顶元素出栈时,先把左孩子压栈,再把右孩子压栈,这和前序遍历应该有的顺序相反,因为这是栈的FILO特性决定的
func preorderTraversal(root *TreeNode) []int {
    if root == nil {
        return []int{}
    }
    // 非递归实现,迭代法
    res := []int{}
    stack := []*TreeNode{}
    stack = append(stack, root)
    for len(stack) != 0 {
        node := stack[len(stack)-1]
        res = append(res, node.Val)
        stack = stack[:len(stack)-1]
        //FILO特性 先入右子树
        if node.Right != nil {
            stack = append(stack, node.Right)
        }
        if node.Left != nil {
            stack = append(stack, node.Left)
        }
    }
    return res
}
  • 后序遍历

    • 后序遍历(左右根),由于第一个入栈的一定是肯定是根节点,所以转换思路,按照根右左的顺序遍历,然后把遍历结果翻转即可
    • 和前序的迭代遍历,只需要改变两步:1.左右子树入栈顺序交换 2.翻转遍历结果
func postorderTraversal(root *TreeNode) []int {
    if root == nil {
            return []int{}
    }
    // 非递归实现,迭代法
    // 左右根
    // 根右左 -> reverse
    res := []int{}
    stack := []*TreeNode{}
    stack = append(stack, root)
    for len(stack) != 0 {
            node := stack[len(stack)-1]
            res = append(res, node.Val)
            stack = stack[:len(stack)-1]
            //FILO特性 先入左子树
            if node.Left != nil {
                    stack = append(stack, node.Left)
            }
            if node.Right != nil {
                    stack = append(stack, node.Right)
            }
    }
    var reverse func(nums []int) []int
    reverse = func(nums []int) []int {
        p, q := 0, len(nums)-1
        for i := 0; i < len(nums)/2; i++ {
        nums[p], nums[q] = nums[q], nums[p]
        p++
        q--
        }
        return nums
    }
    res = reverse(res)
    return res
}
  • 中序遍历
    • 前序遍历的顺序是根左右,先访问的元素是根节点,要输出的元素也是中间节点,访问的元素和要输出的元素顺序是一致的。而中序遍历的迭代法没这么简单,中序遍历要输出节点的顺序是左根右,而先访问的节点却是根节点,先访问的和先输出的不一致,故无法像前、后序列遍历那样简便。
    • 我们需要分清遍历的两个组成部分,1.访问(遍历) 2.输出(处理)
    • 当此刻访问的节点和要输出的节点不一致时,此时我们需要一个辅助指针cur用来表示当前访问的节点,当前访问的节点并不一定是需要立即输出的
func inorderTraversal(root *TreeNode) []int {
    if root == nil {
        return []int{}
    }
    // 左根右
    cur := root
    res := []int{}
    stack := []*TreeNode{}
    for len(stack) != 0 || cur != nil{
        if cur != nil {
            stack = append(stack, cur)
            cur = cur.Left
        } else {
            cur = stack[len(stack)-1]
            stack = stack[:len(stack)-1]
            res = append(res, cur.Val)
            cur = cur.Right
        }
    }
    return res
}