力扣--二叉树知识点

一、理论基础

1、二叉树的种类

(1)满二叉树

        满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。

 这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。

(2)完全二叉树

完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。,若最底层为第h层,则该层包含1~2^h-1个节点。

(3)二叉搜索树 

前面的树都没有数值的,而二叉搜索树是有数值的,二叉搜索树是一个有序树。

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树。

(4)平衡二叉搜索树

 平衡二叉搜索树:又被称为AVL树,且具有性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

2、二叉树的存储方式 

二叉树可以链式存储,也可以顺序存储。那么链式存储⽅式就⽤指针, 顺序存储的⽅式就是⽤数组。

链式存储:

顺序存储:

⽤数组来存储⼆叉树如何遍历的呢?
如果⽗节点的数组下表是 i ,那么它的左孩⼦就是 i * 2 + 1 ,右孩⼦就是 i * 2 + 2
但是⽤链式表示的⼆叉树,更有利于我们理解,所以⼀般我们都是⽤链式存储⼆叉树。

3、二叉树的遍历方式

有两种

a.深度优先遍历:先往深走,遇到叶子节点再往回走。

  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中

这里的前中后,其实指的是中间结点的遍历顺序。

 b:广度优先遍历:层次遍历

二、二叉树的遍历

1、二叉树的递归遍历

递归算法要确定的三要素:

  • 确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值是什么,进而确定递归函数的返回类型。
  • 确定终止条件:写完递归算法,运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然会溢出。
  • 确定单层递归的逻辑。

前中后序遍历的递归法python代码:

# 前序遍历-递归-LC144_二叉树的前序遍历
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 保存结果
        result = []
        
        def traversal(root: TreeNode):
            if root == None:
                return
            result.append(root.val) # 前序
            traversal(root.left)    # 左
            traversal(root.right)   # 右

        traversal(root)
        return result

# 中序遍历-递归-LC94_二叉树的中序遍历
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        result = []

        def traversal(root: TreeNode):
            if root == None:
                return
            traversal(root.left)    # 左
            result.append(root.val) # 中序
            traversal(root.right)   # 右

        traversal(root)
        return result

# 后序遍历-递归-LC145_二叉树的后序遍历
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        result = []

        def traversal(root: TreeNode):
            if root == None:
                return
            traversal(root.left)    # 左
            traversal(root.right)   # 右
            result.append(root.val) # 后序

        traversal(root)
        return result

力扣上题目是关于前中后序的:

  • 144.二叉树的前序遍历
  • 145.二叉树的后序遍历
  • 94.二叉树的中序遍历
  • 589.N叉树的前序遍历
  • 590.N叉树的后序遍历

2、二叉树的迭代遍历

也可以用迭代法来解决上面三道题目:因为递归的实现就是每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

(1)前序遍历(迭代法)

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

迭代的过程中,有两个操作:

  • 处理:将元素放进result数组中
  • 访问:遍历节点

(2)中序遍历

        中序遍历和刚才的前序遍历不能通用的原因在于:因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点。要访问的元素和要处理的元素顺序是一致的,都是中间节点。那么中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层往下访问,直到到达左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

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

 (3)后序遍历

再来看后序遍历,先序遍历是中左右,后序遍历是左右中,那么只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后再反转result数组,输出的结果顺序就是左右中了。

前中后序遍历的迭代法python代码:

# 前序遍历-迭代-LC144_二叉树的前序遍历
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 根结点为空则返回空列表
        if not root:
            return []
        stack = [root]
        result = []
        while stack:
            node = stack.pop()
            # 中结点先处理
            result.append(node.val)
            # 右孩子先入栈
            if node.right:
                stack.append(node.right)
            # 左孩子后入栈
            if node.left:
                stack.append(node.left)
        return result
        
# 中序遍历-迭代-LC94_二叉树的中序遍历
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        stack = []  # 不能提前将root结点加入stack中
        result = []
        cur = root
        while cur or stack:
            # 先迭代访问最底层的左子树结点
            if cur:     
                stack.append(cur)
                cur = cur.left		
            # 到达最左结点后处理栈顶结点    
            else:		
                cur = stack.pop()
                result.append(cur.val)
                # 取栈顶元素右结点
                cur = cur.right	
        return result
        
# 后序遍历-迭代-LC145_二叉树的后序遍历
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        stack = [root]
        result = []
        while stack:
            node = stack.pop()
            # 中结点先处理
            result.append(node.val)
            # 左孩子先入栈
            if node.left:
                stack.append(node.left)
            # 右孩子后入栈
            if node.right:
                stack.append(node.right)
        # 将最终的数组翻转
        return result[::-1]

3、二叉树的层序遍历

层序遍历一个二叉树,就是从左到右一层一层的去遍历二叉树。需要借助一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。而这种层序遍历方式就是图论中的广度优先遍历,只不过应用在二叉树上。

使用队列实现二叉树广度优先遍历:

层序遍历python代码:

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []

        quene = [root]
        out_list = []

        while quene:
            length = len(queue)  
            in_list = []
            for _ in range(length):
                curnode = queue.pop(0)  # (默认移除列表最后一个元素)这里需要移除队列最头上的那个
                in_list.append(curnode.val)
                if curnode.left: queue.append(curnode.left)
                if curnode.right: queue.append(curnode.right)
            out_list.append(in_list)

        return out_list

 力扣上对应题目:

  • 102.二叉树的层序遍历
  • 107.二叉树的层序遍历II
  • 199.二叉树的右视图
  • 637.二叉树的层平均值
  • 429.N叉树的前序遍历
  • 515.在每个树行中找最大值
  • 116.填充每个节点的下一个右侧节点指针
  • 117.填充每个节点的下一个右侧节点指针II

还有一道非常经典的题目:226.翻转二叉树

4.二叉树的深度

(1)最大深度:

        力扣上题目:

  • 104.二叉树的最大深度
  • 559.N叉树的最大深度

(2)最小深度

        力扣上题目:

  • 二叉树的最小深度

二叉树总结的思维导图:

 三、二叉树所要掌握的技能

1、二叉树的理论基础

二叉树的种类、存储方式、遍历方式、定义方式

2、二叉树的遍历方式

  • 深度优先遍历

        二叉树:前中后序递归法(递归三要素)

        二叉树:前中后序迭代法(通过栈模拟递归)

        二叉树:前中后序迭代法(统一风格)

  • 广度优先遍历

        二叉树的层序遍历:通过队列模拟

3、求二叉树的属性

(1)是否对称

  • 递归:后序,比较的是根节点的左子树与右子树是不是相互翻转
  • 迭代:使用队列/栈将两个节点顺序放入容器中进行比较

(2)求最大深度

  • 递归:后序,求根节点最大高度就是最大深度,通过递归函数的返回值做计算树的高度
  • 迭代:层序遍历

(3)求最小深度

  • 递归:后序,求根节点最小高度就是最小深度,注意最小深度的定义
  • 迭代:层序遍历

(4)求有多少个节点

  • 递归:后序,通过递归函数的返回值计算节点数量
  • 迭代:层序遍历

(5)是否平衡

  • 递归:后序,注意后序求高度和前序求深度,递归过程判断高度差
  • 迭代:效率很低,不推荐

(6)找所有路径

  • 递归:前序,方便让父节点指向子节点,涉及回溯处理根节点到叶子的所有路径
  • 迭代:一个栈模拟递归,一个栈来存放对应的遍历路径

(7)递归中如何隐藏着回溯

(8)求左叶子之和

  • 递归:后序,必须三层约束条件,才能判断是否是左叶子
  • 迭代:直接模拟后序遍历

(9)求左下角的值

  • 递归:顺序无所谓,优先左孩子搜索,同时找深度最大的叶子节点
  • 迭代:层序遍历找最后一行最左边

(10)求路径总和

  • 递归:顺序无所谓,递归函数返回值为bool类型是为了搜索一条边,没有返回值是搜索整棵树
  • 迭代:栈里元素不仅要记录节点指针,还要记录从头节点到该节点的路径数值总和

  4、二叉树的修改与构造     

(1)翻转二叉树

  • 递归:前序,交换左右孩子
  • 迭代:直接模拟前序遍历

(2)构造二叉树

  • 递归:前序,重点在于找分割点,分左右区间构造
  • 迭代:比较复杂,意义不大

(3)构造最大的二叉树

  • 递归:前序,分割点为数组最大值,分左右区间构造
  • 迭代:比较复杂,意义不大

(4)合并两个二叉树

  • 递归:前序,同时操作两个树的节点,注意合并的规则
  • 迭代:使用队列,类似层序遍历

5、求二叉搜索树的属性

(1)二叉搜索树中的搜索

  • 递归:二叉搜索树的递归是有方向的
  • 迭代:因为有方向,所以迭代法很简单

(2)是不是二叉搜索树

  • 递归:中序,相当于变成了判断一个序列是不是递增的
  • 迭代:模拟中序,逻辑相同

(3)求二叉搜索树的最小绝对差

  • 递归:中序,双指针操作
  • 迭代:模拟中序,逻辑相同

(4)求二叉搜索树的众数

  • 递归:中序,清空结果集的技巧,遍历一遍便可求众数集合
  • 迭代:模拟中序,逻辑相同

(5)二叉搜索树转成累加树

  • 递归:中序,双指针操作累加
  • 迭代:模拟中序,逻辑相同

6、二叉树公共祖先问题

(1)二叉树的公共祖先问题

  • 递归:后序、回溯,找到左子树出现目标值,右子树节点目标值的节点
  • 迭代:不适合模拟回溯

(2)二叉搜索树的公共祖先问题

  • 递归:顺序无所谓,如果节点的数值在目标区间就是最近公共祖先
  • 迭代:按序遍历

7、二叉搜索树的修改与构造

(1)二叉搜索树中的插入操作

  • 递归:顺序无所谓,通过递归函数返回值添加节点
  • 迭代:按序遍历,需要记录插入父节点,这样才能做插入操作

(2)二叉搜索树中的删除操作

  • 递归:前序,想清楚删除非叶子节点的情况
  • 迭代:有序遍历,较复杂

(3)修剪二叉搜素树

  • 递归:前序,通过递归函数返回值删除节点
  • 迭代:有序遍历,较复杂

(4)构造二叉搜索树

  • 递归:前序,数组中间节点分割
  • 迭代:较复杂,通过三个队列来模拟

四、总结

  • 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点
  • 求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算
  • 求二叉搜索树的属性,一定是中序,要利用有序性

注意在普通二叉树的属性中,一般用后序,例如单纯求深度就用前序,二叉树:找所有路径也用前序,旨在让父节点指向子节点。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值