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

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

前言

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

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

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

1. 二叉树的前序遍历

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

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

题目描述

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

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

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

代码

/**
 * 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. 二叉树的中序遍历

题目描述

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

代码

/**
 * 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. 二叉树的后序遍历

题目描述

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

代码

/**
 * 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. 前序遍历的非递归实现

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

代码与思路

/**
 * 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. 中序遍历的非递归实现

代码与思路

/**
 * 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. 后序遍历的非递归实现

代码与思路

/**
 * 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,难受)按照刚刚分析出来的思路实现后序遍历。

总结

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

相关文章
|
27天前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
110 14
|
2月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
64 4
|
2月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
67 10
|
2月前
|
存储 算法 数据可视化
【二叉树遍历入门:从中序遍历到层序与右视图】【LeetCode 热题100】94:二叉树的中序遍历、102:二叉树的层序遍历、199:二叉树的右视图(详细解析)(Go语言版)
本文详细解析了二叉树的三种经典遍历方式:中序遍历(94题)、层序遍历(102题)和右视图(199题)。通过递归与迭代实现中序遍历,深入理解深度优先搜索(DFS);借助队列完成层序遍历和右视图,掌握广度优先搜索(BFS)。文章对比DFS与BFS的思维方式,总结不同遍历的应用场景,为后续构造树结构奠定基础。
156 10
|
2月前
|
Go 索引 Perl
【LeetCode 热题100】【二叉树构造题精讲:前序 + 中序建树 & 有序数组构造 BST】(详细解析)(Go语言版)
本文详细解析了二叉树构造的两类经典问题:通过前序与中序遍历重建二叉树(LeetCode 105),以及将有序数组转化为平衡二叉搜索树(BST,LeetCode 108)。文章从核心思路、递归解法到实现细节逐一拆解,强调通过索引控制子树范围以优化性能,并对比两题的不同构造逻辑。最后总结通用构造套路,提供进阶思考方向,帮助彻底掌握二叉树构造类题目。
142 9
|
3月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
92 9
 算法系列之数据结构-二叉树
|
5月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
189 2
|
6月前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
106 5
|
7月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
9月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
126 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行

热门文章

最新文章