【LeetCode 算法专题突破】二叉树的深度优先遍历(⭐)

文章目录

前言

接下来我要开始攻克二叉树这一个大难题了,我打算把二叉树分成四个部分进行总结:

  1. 二叉树的深度优先遍历
  2. 二叉树的广度优先遍历(也叫层序遍历)
  3. 二叉树的基本属性求解
  4. 二叉树其他相关问题(删改、求公共祖先、二叉搜索树等等)

那我也不废话了,直接开始。

1. 二叉树的前序遍历

接下来,我们就将二叉树的前中后序的递归遍历都做一遍,然后再分别将这四种遍历的迭代实现方法也做一遍,基础不牢,地动山摇,我们慢慢来,一步一个脚印学好二叉树!

题目链接:144. 二叉树的前序遍历

题目描述

所谓的前中后序遍历,在前中后的是什么?其实就是根节点,所以我一般习惯用一个口诀来记住前中后序的遍历顺序:

  1. 前序就是:根左右
  2. 中序就是:左根右
  3. 后序就是:左右根

这其实就是按照根在遍历中的顺序划分的遍历方式。来看代码:

代码

go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func preorderTraversal(root *TreeNode) (res []int) {
    var preorder func(node *TreeNode)
    preorder = func(node *TreeNode) {
        if node == nil {
            return
        }
        res = append(res, node.Val) // 根
        preorder(node.Left)  // 左
        preorder(node.Right) // 右
    }
    preorder(root) // 调用前序遍历
    return res
}

我们来看这个代码,其实这就是一个简单的递归遍历的函数,所谓的 "根左右" 其实就是先把 "根" 位置的值给塞进 res 数组而已。

2. 二叉树的中序遍历

那就继续中序遍历

题目链接:94. 二叉树的中序遍历

题目描述

其实刚刚已经介绍过前中后序是怎么样的了,所以我们直接看代码:

代码

go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func inorderTraversal(root *TreeNode) (res []int) {
    var inorder func(node *TreeNode)
    inorder = func(node *TreeNode) {
        if node == nil {
            return 
        }
        inorder(node.Left)
        res = append(res, node.Val)
        inorder(node.Right)
    }
    inorder(root)
    return res
}

这里我注释也懒得打了,可以发现他其实比起前序遍历就只是改了一个地方的代码,就是把 根 的位置和向左递归的位置换了一下,这就是中序遍历的遍历方法。

3. 二叉树的后序遍历

一做肯定是得做全套滴

题目链接:145. 二叉树的后序遍历

题目描述

其实现在题目描述也没有什么意义了,知道是后序遍历,我们直接写代码就完了:

代码

go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func postorderTraversal(root *TreeNode) (res []int) {
    var postorder func(node *TreeNode) 
    postorder = func(node *TreeNode) {
        if node == nil {
            return 
        }
        postorder(node.Left)
        postorder(node.Right)
        res = append(res, node.Val)
    } 
    postorder(root)
    return res
}

没有意外,也是朴实无华的改个代码的位置。

现在开胃小菜算是做完了,像前中后这样的遍历其实并没有什么难度,所以我们还需要挑战一下前中后序的非递归遍历,不然不能算是真正学会了这三种遍历的方式。既是锻炼我们的代码能力,也是让我们熟悉二叉树的基本遍历方式,打牢基础,后面才不会越看越懵逼。

4. 前序遍历的非递归实现

这里我就不把题目描述给放出来了,这里我们用的就是第一题

代码与思路

go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func preorderTraversal(root *TreeNode) (ans []int) {
    if root == nil {
        return
    }
    st := list.New()
    st.PushBack(root)
    for st.Len() > 0 {
        node := st.Remove(st.Back()).(*TreeNode)
        ans = append(ans, node.Val)
        if node.Right != nil {
            st.PushBack(node.Right)
        }
        if node.Left != nil {
            st.PushBack(node.Left)
        }
    }
    return ans
}

我来讲讲使用迭代法求解的一个流程,采取的是用一个栈来模拟递归的方法(这里我采用的是 go 语言的 list 来模拟栈操作),模拟前序遍历 "根左右" 的遍历方式

  1. 首先将根(或者说当前走到的节点)的值存入数组
  2. 然后分别将右节点和左节点入栈,因为出栈的顺序是相反的,所以我们入栈的时候就要这样先入右节点再入左节点,这样出栈遍历的时候就能达成 "根左右" 的遍历方式了

其实核心的步骤就是这两步,那我们继续实现中序的非递归遍历

5. 中序遍历的非递归实现

代码与思路

go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func inorderTraversal(root *TreeNode) (ans []int) {
    st := list.New()
    cur := root
    for cur != nil || st.Len() > 0 {
        if cur != nil {
            st.PushBack(cur)
            cur = cur.Left
        } else {
            cur = st.Remove(st.Back()).(*TreeNode)
            ans = append(ans, cur.Val)
            cur = cur.Right
        }
    }
    return ans
}

前序遍历的时候,我们就是不断计算根位置的值,然后只管用栈按顺序遍历二叉树就行了,但是到了中序遍历,我们需要取的是左节点的值,所以我们就需要在左节点遍历的时候边遍历边取值,我们来根据代码梳理一下他的流程

  1. 将当前节点入栈,然后一直往左遍历直到走到 nil
  2. 走到 nil 之后,上一个节点,也就是栈顶的元素就是最左节点,输出到 ans
  3. 然后往右遍历一步,持续这个三步循环

这三步模拟的就是 "左根右" 的遍历方式,刚好也是三步,找到最左,取根,找右,循环往复,就能完成中序遍历了。迭代算法确实不太好想,但是上手模拟一遍还是比较好理解的。

6. 后序遍历的非递归实现

代码与思路

go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func postorderTraversal(root *TreeNode) (ans []int) {
    if root == nil {
        return
    }
    st := list.New()
    st.PushBack(root)
    for st.Len() > 0 {
        node := st.Remove(st.Back()).(*TreeNode)
        ans = append(ans, node.Val)
        if node.Left != nil {
            st.PushBack(node.Left)
        }
        if node.Right != nil {
            st.PushBack(node.Right)
        }
    }
    reverse(ans)
    return ans
}

func reverse(a []int) { // 反转数组
    l, r := 0, len(a)-1
    for l < r {
        a[l], a[r] = a[r], a[l]
        l, r = l+1, r-1
    }
} 

后序遍历有一个非常巧妙的解法:因为前序遍历是 "根左右",后续遍历是 "左右根",如果我们根据 "根右左" 的顺序来进行遍历,然后再将结果进行反转就能将 "根右左" -> "左右根",这样就完成了后续遍历,也就是我们只需要修改一下前序遍历的代码即可

就那上述代码来说,我将遍历左右的顺序进行了调换,然后实现了一个 reverse 反转数组(go 语言没有 C++ 那样提供 STL,难受)按照刚刚分析出来的思路实现后序遍历。

总结

基础不牢,地动山摇,把二叉树的前中后序学会,我们下一节挑战二叉树的广度优先遍历,又或者说,二叉树的层序遍历。

相关推荐
倒头就睡的小比特4 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼4 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光4 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark4 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her14 天前
并查集-听课笔记
笔记·算法·并查集
码流子4 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven4 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark4 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218614 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法