首页 > 编程语言 >一套模板搞定二叉树算法题--二叉树算法讲解002

一套模板搞定二叉树算法题--二叉树算法讲解002

时间:2024-01-13 23:44:09浏览次数:27  
标签:左子 遍历 -- 前序 start 算法 二叉树

1、二叉树的递归

递归:
mark

2、二叉树遍历之DFS深度优先遍历

2.1、遍历的概念

mark

每个节点 都要恰好被访问一次,本质上是二叉树的线性化

一个树形的结构,线性化为一个数组之类的"串"的结构。

2.2、DFS深度优先遍历

mark

示例二叉树原型图:
mark

2.2.1、前序遍历

前序遍历执行顺序:
根节点--对左子树做前序遍历--对右子树做前序遍历

总的顺序:根节点--左子树--右子树
左子树中:根-左-右
根节点
右子树中:根-左-右

mark

对A的左子树做前序遍历
mark

A的左子树的根节点是B
mark

对B的左子树做前序遍历
mark

对B的右子树做前序遍历
mark

mark

对E的左子树前序遍历
mark

至此,A的左子树做完了前序遍历:
mark

然后,对A的右子树做前序遍历:
mark

mark

至此,二叉树的前序遍历完成。
mark

我们会发现,整个深度优先的遍历过程都是 递归的。

2.2.2、中序遍历

中序遍历执行顺序:
对左子树做中序遍历--根节点--对右子树做中序遍历

总的顺序:左子树--根节点--右子树
左子树中:左--根-右
根节点
右子树中:左-根-右

2.2.3、后序遍历

后序遍历执行顺序:
对左子树做后续遍历--对右子树做后续遍历--根节点

总的顺序:左子树--右子树--根节点
左子树中:左--右-根
根节点
右子树中:左-右-根

2.2.4、总结

所谓前序、中序、后序的区别。
就是根在前、根在中、还是根在后?
左、右的顺序都是不变的,从左到右。

mark

3、DFS深度优先遍历之代码实现

mark

4、二叉树三种深度遍历

4.1 leetcode 144 前序遍历

mark

mark

4.2 leetcode 94 中序遍历

mark

4.3 leetcode 145 后序遍历

mark

5、从深度遍历序列还原二叉树--经典题

5.1、leetcode105 从前序与中序遍历序列构造二叉树

题目:
mark

题意:
mark

题解思路:
mark

前序:
前序的根:
mark

前序的根确定为3
mark

再根据中序确定左右子树
mark

mark

mark

根据前序和中序的遍历规则确定20为右子树的根:
mark

总结步骤
1、根据提供的前序数组的第一个元素,确定二叉树的根节点
2、找到根节点后,在中序数组中,根据根节点切割左右
左边为二叉树左子树内容,右边为二叉树右子树内容
3、再将中序数组切割的左右,返回给前序,在重复步骤1、2做递归操作

再来一个示例树讲解步骤,强调递归的体现:
mark

找到根节点3和左子树、右子树
mark

mark

递归右子树:
mark

右子树的根节点20和左子树、右子树
mark

mark

核心思路:
其实是个子数组的过程,即把2个大数组(前序和中序数组)不断拆成更小的数组的过程
1个大数组的拆分过程,可以使用2个指针来做拆分这件事
实现过程中重要的3个指针:
pre_start、in_start、in_end、同时也会用到代表根节点的idx

3个指针的含义:
mark
图解:
mark

mark

递归:
下一次递归左子树时:
pre_start 是 pre_start+1
in_start还是in_start不变
in_end是idx-1

下一次递归右子树时:
pre_start 是 pre_start + (idx - in_start)+ 1
in_start是idx+1
in_end还是in_end

这样我们就可以实现递归了。

可以再看一个类似的示例图:
mark

mark

题解:
mark

mark

# 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 buildTree(self, preorder: List[int], inorder: List[int], ) -> Optional[TreeNode]:
        # 构建一个哈希表,key为节点的值,value为节点在中序遍历数组中的索引
        # 方便直接通过节点值取到下标
        dic = {val: i for i, val in enumerate(inorder)}
        n = len(inorder)
        # 递归入口
        return self.help(dic, preorder, inorder, 0, 0, n-1)

def help(self, dic, preorder, inorder, pre_start, in_start, in_end):
    # 递归终止条件:若遍历区间不存在,返回空节点
    if in_start > in_en
    
    
    
        return None

    # 获得当前区间的根节点的值node_val,为preorder[pre_start]
    node_val = preorder[pre_start]
    # 获得该节点在中序遍历数组中的位置
    idx_node = dic[node_val]

    # 构建节点node
    node = TreeNode(node_val)
    
    
    # 进行递归

    # pre_start
    # ↓
    # 3 | 9  5 | 20  15  7
    #     ↑       ↑             左子树和右子树的pre_start

    # in_start        in_end
    # ↓               ↓
    # 9  5 | 3 | 15  20  7
    #        ↑
    #        idx_node

    # 9  5 | 3 | 15  20  7
    # ↑  ↑                      左子树的in_start和in_end
    #             ↑      ↑      右子树的in_start和in_end
    
    node.left = self.help(dic, preorder, inorder, pre_start + 1, in_start, idx_node - 1)
    node.right = self.help(dic, preorder, inorder, pre_start + (idx_node - in_start) + 1, idx_node + 1, in_end)

    # 将该节点回传
    return node

注:
代码中的 {val: i for i, val in enumerate(inorder)} 表示我们将 inorder 列表中的每个元素作为字典的键,将其索引作为对应的值。

例如,如果 inorder 是 [4, 2, 7, 5, 1, 3, 6] ,那么生成的字典 dic 将是 {4: 0, 2: 1, 7: 2, 5: 3, 1: 4, 3: 5, 6: 6} 。

5.2、leetcode106 从中序与后序遍历序列构造二叉树

题目:
mark

mark

题解:
mark

5.3、2023C-二叉树的广度优先遍历

题目:
mark

题意和思路:
先根据中序和后序遍历构造二叉树,再进行二叉树的层序遍历
相当于leetcode106和leetcode102这2题的组合。
mark

6、二叉搜索树

6.1、二叉搜索树的概念和性质

mark

6.2、二叉搜索树的查找

mark

查找n次,每次有2个分支中的1个;
即为: \(2^n = k\)
\(n = log_2^k\)

每次查找只进入2个分支中的1个,所以时间复杂度为O(log(n))
可以理解为一种特殊的二分查找,和二分查找的时间复杂度是一样的。
或者说二叉搜索树是二分查找在树形结构上的体现

6.2.1、二叉搜索树查找代码模板

mark

6.2.2、二叉搜索树查找--leetcode 700

题目和题意:
mark

题解:
注意思考,递归子问题为什么要return?
mark

如果对上述的return的写法不熟悉,可以改为如下使用成员变量的写法:
初始化成员变量 self.ans = None
mark

mark

6.3、二叉搜索树的增加

mark

6.3.1、二叉搜索树的增加 -- leetcode 701

题目和题意:
mark

题解:
mark

6.3.2、二叉搜索树的增加 -- 2023C 计算三叉搜索树的高度

题目和题意
mark

题解:
这题其中关键部分的解法(树的插入部分)和 leetcode 701几乎一样。
mark

6.3.3、二叉搜索树的增加 -- leetcode 98 验证二叉搜索树

题目:
mark

mark

解题思路:
用二叉搜索树的性质
①、先中序遍历出树
②、再判断树的值是否从小到大排列的。
mark

其中步骤1就是leetcode94 中序遍历二叉树。
题解:
mark

注:
步骤1中序遍历二叉树可以这样实现
mark

也可以这样回传列表的方式实现 实现的方式多种多样
mark

7、总结

mark


注:
文中截图源自大佬: 闭着眼睛学数理化 课程内容

标签:左子,遍历,--,前序,start,算法,二叉树
From: https://www.cnblogs.com/xlfcjx/p/17963223

相关文章

  • 面向对象之继承
    【一】什么是继承新建的类可以继承一个或多个父类,子类有所有父类有点数据属性和函数属性python中继承被分为单继承和多继承【二】单继承和多继承#定义父类classHuman:...#定义父类classAsia:...#单继承classChinese(Human):...#多继承c......
  • 在WSL2下的Ubuntu常用命令
    #查看宿主主机IPiproute|grepdefault|awk'{print$3}'cat/etc/resolv.conf#查看本机IPipa|grep"globaleth0"hostname-I|awk'{print$1}'#安装MySQL客户端sudoapt-getinstallmysql-client #保留文件属性的多文件或文件夹的压缩及解压tar--xattrs--x......
  • 面向对象之派生
    【一】什么是派生派生是指子类继承父类,子类多出来自己的属性和方法,并且重用父类的属性和方法【二】派生的方法子类可以派生出自己的新属性,在进行属性查找时,子类的属性名会优先于父类被查找classHuman:location='earth'def__init__(self,country,name):......
  • 面向对象之抽象类
    【一】什么是抽象类抽象类是一种不能被实例化的类,它充当了一种模板或者说是蓝图。在抽象类中,你可以定义一些抽象方法,这些抽象方法没有具体的实现,即没有方法体。它们必须在抽象类的子类中被实现,除非那个子类也是一个抽象类。抽象类可以包含具体方法(已实现的方法)和抽象方法(未实现......
  • 面向对象之多态和鸭子类型
    【一】多态【1】什么是多态指一类事物有很多形态【2】示例比如汽车是一类,它于很多形态,如奔驰宝马奥迪classCar:defrun(self):print('开始跑')classBenz(Car):defrun(self):super().run()print('98加满')classBwm(Car......
  • 我只是想安静的考过高项,却发现需要XMind帮忙。搞定XMind
    我只是想安静的考过高项,却发现需要XMind帮忙。高项这玩意居然能和职称挂钩,幸好不是编制内的人,否则真的要无力吐槽。既然决定要死磕一次高项,就认证准备吧。欲善其功,欲善其工必先利其器。我先搞定XMind这玩意儿。XMind不开VIP功能有限,可VIP的价格又让人望而生畏,于是我找到了一个......
  • 面向对象之反射
    【一】反射【1】什么是反射反射是一种程序可以判断,取出和修改其本地状态或行为的能力在python中,反射主要是指通过字符串操作对象属性【2】Python中的反射同过字符串的形式操作对象相关的属性python一切皆为对象,都可以使用反射【二】反射方法【1】反射方法介绍getatt......
  • 面向对象之绑定方法和非绑定方法
    【一】绑定方法与非绑定方法介绍【1】绑定方法绑定给谁,谁调用就将谁作为第一个参数传入(1)绑定到类的方法使用classmethod装饰器来装饰将类作为第一个参数传入对象也可调用,会将实例化对象的类作为第一个参数传入(2)绑定到对象的方法通过对象.方法的方法实现,将对象作为......
  • 面向对象之元类
    【一】常用的魔法方法【1】初始化对象的属性__init__【二】元类【1】什么是元类一切源于一句话:Python中一切皆对象八大基本数据类型是对象类实例化得到的对象也是对象其实类本身也是一种对象classHuman:def__init__(self,name,age):self.name=name......
  • 对单例模式的理解
    【零】引入【1】前言我们知道,经典设计模式总共有23种,但其中只有少数几种被广泛采用。根据我的工作经验,实际常用的可能不超过其中的一半。如果随机找一位程序员,并要求他列举出自己最熟悉的三种设计模式,那么单例模式肯定会是其中之一,这也是今天我们要讨论的。【2】为什么要......