代码随想录算法训练营第14天 | 二叉树理论基础 递归遍历 迭代遍历

系列文章目录

代码随想录——二叉树篇



二叉树的基础知识

二叉树的种类

在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。

满二叉树

满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
深度为k的满二叉树,拥有2^k-1个节点

完全二叉树

完全二叉树的定义如下:
1.在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值;2.并且最下面一层的节点都集中在该层最左边的若干位置。
完全二叉树
之前我们刚刚讲过优先级队列其实是一个堆堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
(堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆.)

二叉搜索树

前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
左子结点.值 < 根节点.值 < 右子结点.值
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树
下面这两棵树都是搜索树
二叉搜索树

平衡二叉搜索树

平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过****1并且左右两个子树都是一棵平衡二叉树
最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。
平衡二叉树
C++中****map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn
unordered_map、unordered_set,unordered_map、unordered_map底层实现是哈希表。

二叉树的存储方式

二叉树可以链式存储,也可以顺序存储。

那么链式存储方式就用指针, 顺序存储的方式就是用数组。

顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。

链式存储如图:
二叉树链式储存
链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢?

其实就是用数组来存储二叉树,顺序存储的方式如图:
二叉树的顺序储存
用数组来存储二叉树如何遍历的呢?

如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。

所以大家要了解,用数组依然可以表示二叉树。

二叉树的遍历方式

二叉树主要有两种遍历方式:

深度优先遍历dfs:先往深走,遇到叶子节点再往回走。
广度优先遍历bfs:一层一层的去遍历。

那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:

深度优先遍历:
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)

广度优先遍历:
层次遍历(迭代法)

在深度优先遍历中:有三个顺序,前中后序遍历, 有同学总分不清这三个顺序,经常搞混,我这里教大家一个技巧。

这里前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。

看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式

前序遍历:中左右
中序遍历:左中右
后序遍历:左右中
大家可以对着如下图,看看自己理解的前后中序有没有问题。
深度优先遍历中的前中后序遍历

二叉树结点的写法

我们来看看链式存储的二叉树节点的定义方式。二叉树的定义 和 链表 是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子。
二叉树的结点,有一个val, 有两个指针

class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

递归遍历

递归怎么写:

  1. 确定参数
  2. 确定递归终点
  3. 确定单层递归的逻辑

前序

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def dfs(node):
            if not node: return
            res.append(node.val)
            dfs(node.left)
            dfs(node.right)

        dfs(root)
        return res

中序

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def dfs(node):
            if not node: return
            dfs(node.left)
            res.append(node.val)
            dfs(node.right)

        dfs(root)
        return res

后序

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def dfs(node):
            if not node: return
            dfs(node.left)
            dfs(node.right)
            res.append(node.val)
        dfs(root)
        return res

迭代遍历

前序(迭代遍历)

前序遍历是根左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。

为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
先把根节点加入栈内,只要栈不空,弹出栈顶元素,用curr记录一下,并把它的值加入Res数组内;接着判断一下右孩子是否存在,存在加入栈;判断左孩子是否在,存在加入栈,然后重复执行这个动作
在这里插入图片描述

def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root: return []
        res, stack = [], []
        stack.append(root)
        while len(stack):
            curr = stack.pop()
            res.append(curr.val)
            if curr.right:
                stack.append(curr.right)
            if curr.left:
                stack.append(curr.left)
        return res

中序(迭代遍历)

中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root: return []
        res, stack = [],[]
        curr = root
        while curr or len(stack):
            if curr != None:
                stack.append(curr)
                curr = curr.left
            else:
                curr = stack.pop()
                res.append(curr.val)
                curr = curr.right
        return res

后序(迭代遍历)

前序: 根左右 ——> 根右左——>左右根

代码逻辑,只要把左子结点和右子结点的入栈顺序调个个儿,res 数组存储的就是根右左的遍历顺序,然后把res数组颠倒输出就行了

def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root: return []
        res, stack = [], []
        stack.append(root)
        while len(stack):
            curr = stack.pop()
            res.append(curr.val)
            if curr.left: stack.append(curr.left)
            if curr.right: stack.append(curr.right)
        res = res[::-1]
        return res

二叉树的统一迭代写法

这个我实在没有余力短时间吸收掌握
参考链接二叉树统一迭代写法

二叉树遍历方式总结

dfs

迭代方法和递归方法遍历二叉树其实都是dfs, 经常笔试题常用的dfs其实经常使用递归去做

递归方法

前序,中序,后序

迭代方法

前序,后序,中序

bfs

在下一个博客

这些遍历方式的复杂度参考二叉树遍历方式的复杂度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树理论基础递归遍历迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值