当前位置: 首页 > article >正文

【Python 数据结构 11.二叉搜索树】

人永远在原谅自己,以为这样就可以有重来的余地

                                                                        —— 25.3.9

一、二叉搜索树的基本概念

1.二叉搜索树的概念

Ⅰ、定义

二叉搜索树(又称为二叉排序树,二叉查找树),它满足如下四点性质:

        1) 空树是二叉搜索树;

        2) 若它的左子树不为空,则子树上所有结点的值均小于结点的值;

        3) 若它的右子树不为空,则子树上所有结点的值均大于结点的值;

        4) 它的左右子树均为二叉搜索树;

        如图所示,对于任何一颗子树而言,它的结点的值一定大于子树所有结点的值,且一定小于子树所有结点的值


Ⅱ、用途

        从二叉搜索树的定义可知,它的前提是二叉树,并且采用了递归的方式进行定义,它的结点间满足一个偏序关系:左子树根结点的值一定比父结点小右子树根结点的值一定比父结点大

        正如它的名字所说,构造这样一棵树的目的是为了提高搜索的速度,如果对二叉搜索树进行中序遍历,我们可以发现,得到的序列是一个递增序列。


2.二叉搜索树的链式存储

         我们一般用孩子表示法来定义一棵二叉搜索树的结点。

        (1) 二叉搜索树需要有一个结点值,也就是数据域,注意:这里的类型其实可以是任意类型,只要这种类型支持 关系运算符 的比较即可

        (2) 二叉搜索树结点的左儿子结点的指针,没有左儿子结点时,置为空;

        (3) 二叉搜索树结点的右儿子结点的指针,没有右儿子结点时,置为空;


3.二叉搜索树的结点查找

 Ⅰ、节点查找的概念

        二叉搜索树的查找指的是:在树上查找某个数是否存在,存在返回 true ,不存在返回 false

        如图所示,代表的是从一个二叉搜索树中查找出一个值为 3 的结点。一开始,3 比根节点 5 小,于是递归访问左子树;并且比子树的根节点 4 小,于是继续递归访问左子树;这时候比根节点 2 大,于是递归访问右子树,正好找到值为 3 的查找,回溯结束查找


Ⅱ、结点查找的步骤

对于要查找的数 val,从根结点出发,总共四种情况依次判断:

        第1步:若为空树,直接返回 false;

        第2步:val 的值 小于 树根结点的值,说明 val 对应的结点不在根结点,也不在右子树上,则递归返回左子树的 查找 结果;

        第3步:val 的值 大于 树根结点的值,说明 val 对应的结点不在根结点,也不在左子树上,则递归返回右子树的 查找 结果;

        第4步:直接返回 true(无须等于判定,因为不小于、不大于必然是等于);


4.二叉搜索树的结点插入

Ⅰ、结点插入的概念

        二叉搜索树的插入指的是将给定的值生成结点后,插入到树上的某个位置,并且保持这棵树还是二叉搜索树。

        如图所示,代表将一个值为 3 的结点插入到一个二叉搜索树中。一开始,3 比 根节点 5 小,于是递归插入左子树;还是比子树的根节点 4 小,于是继续递归插入左子树;这时候比根节点 2 大,于是递归插入右子树,右子树为空,则直接生成一个值为 3 的结点,回溯结束插入


Ⅱ、结点插入的步骤

对于要插入的数 val ,从根结点出发,总共四种情况依次判断:

        第1步:若为空树,则创建一个值为 val 的结点并且返回;

        第2步:val 的值 小于 树根结点的值,那么插入位置一定在 左子树递归执行插入左子树的过程,并且返回插入结果作为新的左子树;

        第3步:val 的值 大于 树根结点的值,那么插入位置一定在 右子树递归执行插入右子树的过程,并且返回插入结果作为新的右子树;

        第4步:直接返回当前树的根结点;


5.二叉搜索树的结点删除

Ⅰ、结点删除的概念

        二叉搜索树的删除指的是在树上删除给定值的结点

        如图所示,从这棵树删除根节点 5 的过程。首先,由于它有左右儿子结点,所以这个过程,根结点并不是真正地删除。而是从右子树中找到最小的结点 6,替换根结点,并且从根节点为 7 的子树中删除 6 的过程,由于 6 没有子节点,所以这个过程结束了


Ⅱ、结点删除的步骤

        删除值为 val 的结点的过程,从根结点出发,总共七种情况依次判断:

        第1步:如果当前结点为,则直接返回空树

        第2步:如果要删除的值小于当前结点的值,则递归调用左子树进行结点的删除

        第3步:如果要删除的值大于当前结点的值,则递归调用右子树进行结点的删除

        第4步:如果当前结点是一个叶子结点,即它没有左子树和右子树,那么删除该结点,并且
回空树

        第5步:如果当前结点只有右子树而没有左子树,那么删除当前结点并将右子树返回

        第6步:如果当前结点只有左子树而没有右子树,那么删除当前结点并将左子树返回

        第7步:如果当前结点既有左子树又有右子树,那么找到当前结点右子树中的最小值结点(即最左边的结点),将当前结点的值替换为这个最小值结点的值,然后递归地删除右子树中该最小值结点。


6.二叉搜索树的总结

        纵观二叉搜索树的查找、插入 和 删除。完全取决于二叉搜索树的形状,如果是完全二叉树或者接近完全二叉树,则这三个过程都是 (logn) 的,如果是斜树,则三个过程近似操作线性表为  O(n) 


二、Python中的二叉搜索树

1.定义二叉搜索树结点类

val:存储树结点的索引

left:存储树结点的左孩子

right:存储树结点的右孩子 

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

2.定义二叉搜索树类

Ⅰ、树初始化

        只需要初始化一个根结点为空值 None

class BinarySearchTree:
    def __init__(self):
        self.root = None

Ⅱ、 添加结点

        传入根结点和要添加结点的val,比较二者大小关系,递归插入

    # 增
    def insertNode(self, node, val):
        # 递归终止条件
        if node is None:
            return TreeNode(val)
        if val < node.val:
            node.left = self.insertNode(node.left, val)
        elif val > node.val:
            node.right = self.insertNode(node.right, val)
        return node

    def insert(self, val):
        self.root = self.insertNode(self.root, val)

Ⅲ、删除结点

        递归删除,递归过程如下图:

    # 删
    def removeNode(self, node, val):
        if node is None:
            return None
        if val < node.val:
            node.left = self.removeNode(node.left, val)
        elif val > node.val:
            node.right = self.removeNode(node.right, val)
        else:
            if node.left is None and node.right is None:
                return None
            elif node.left is None:
                return node.right
            elif node.right is None:
                return node.left
            else:
                replacement = node.right
                while replacement.left:
                    replacement = replacement.left
                node.val = replacement.val
                node.right = self.removeNode(node.right, replacement.val)
        return node

    def remove(self, val):
        self.root = self.removeNode(self.root, val)

Ⅳ、查找结点

        判断传入结点的val值与要查找的val值大小关系,已知二叉搜索树结点间的相互关系,遍历推断所查找结点的位置 

    # 查
    def searchNode(self, node, val):
        if node is None:
            return False
        if val < node.val:
            return self.searchNode(node.left, val)
        elif val > node.val:
            return self.searchNode(node.right, val)
        return True

    def search(self, val):
        return self.searchNode(self.root, val)

Ⅴ、中序遍历

        已知中序遍历的顺序是:左 - 根 - 右,而结合二叉搜索树结点间的相互关系,进行中序遍历的结果恰好应该是一个递增序列 

    # 中序遍历
    def inOrder(self, node):
        if node is None:
            return
        self.inOrder(node.left)
        print(node.val, end=",")
        self.inOrder(node.right)

    def inOrderTraversal(self):
        self.inOrder(self.root)
        print("")

Ⅵ、代码测试

添加结点后的二叉搜索树: 

删除结点 5 后的二叉搜索树:

def Test():
    bst = BinarySearchTree()

    bst.insert(5)
    bst.insert(3)
    bst.insert(7)
    bst.insert(2)
    bst.insert(4)
    bst.insert(6)
    bst.insert(8)
    bst.inOrderTraversal()
    bst.remove(5)
    bst.inOrderTraversal()
    print(bst.search(4))
    print(bst.search(9))

Test()


三、实战

1.108. 将有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。

示例 1:

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

示例 2:

输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 按 严格递增 顺序排列

方法一、递归

思路与算法

递归终止条件:如果左边界 l 大于右边界 r,说明当前子数组为空,返回 None

选择根节点:计算当前子数组的中间位置 mid = (l + r) // 2。以 nums[mid] 作为根节点的值,创建一个 TreeNode

递归构建左子树和右子树:左子树的范围是 [l, mid - 1],递归调用 inOrder 构建左子树。右子树的范围是 [mid + 1, r],递归调用 inOrder 构建右子树。

返回根节点:返回当前构建的根节点。

# 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 inOrder(self, nums, l, r):
        if l > r:
            return None
        mid = (l + r) // 2
        root = TreeNode(nums[mid])
        root.left = self.inOrder(nums, l, mid - 1)
        root.right = self.inOrder(nums, mid + 1, r)
        return root

    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        root = nums[0]
        return self.inOrder(nums, 0, len(nums) - 1)


2.98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:root = [2,1,3]
输出:true

示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

提示:

  • 树中节点数目范围在[1, 104] 内
  • -231 <= Node.val <= 231 - 1

方法一 根据二叉搜索树中序遍历的性质判断

思路与算法

中序遍历:使用递归方法 inorder 对二叉树进行中序遍历,将节点值按顺序存储在 self.list 中。

中序遍历的顺序是:左子树 -> 根节点 -> 右子树。

检查递增性:遍历 self.list,检查每个元素是否大于前一个元素。如果发现某个元素小于或等于前一个元素,则返回 False,说明不是有效的二叉搜索树。​

返回结果:如果列表严格递增,返回 True,说明是有效的二叉搜索树。

# 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 inorder(self, root):
        if root:
            self.inorder(root.left)
            self.list.append(root.val)
            self.inorder(root.right)

    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        self.list = []
        self.inorder(root)
        for i in range(1, len(self.list)):
            if self.list[i] <= self.list[i - 1]:
                return False
        return True

 


3.面试题 04.05. 合法二叉搜索树

实现一个函数,检查一棵二叉树是否为二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出:true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出:false
解释:输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4 。

方法一 递归

思路与算法

递归终止条件:如果当前节点为空,返回 True,因为空树是有效的二叉搜索树。

检查当前节点:如果当前节点的值不在 (min, max) 范围内,返回 False。​

递归检查左子树和右子树:左子树的范围是 (min, root.val),因为左子树的所有节点值必须小于当前节点的值。右子树的范围是 (root.val, max),因为右子树的所有节点值必须大于当前节点的值。

返回结果:如果左子树和右子树都满足条件,返回 True,否则返回 False

# 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 checkTree(self, root, min, max):
        if not root:
            return True
        if root.val >= max or root.val <= min:
            return False
        return self.checkTree(root.left, min, root.val) and self.checkTree(root.right, root.val, max)

    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        return self.checkTree(root, -inf, inf)


http://www.kler.cn/a/578666.html

相关文章:

  • Hytrix深入学习
  • 前端 | CORS 跨域问题解决
  • 基于ESP32的Python物联网开发实践 - 通过HTTP API控制LED灯
  • Windows下配置Flutter移动开发环境以及AndroidStudio安装和模拟机配置
  • 策略设计模式-下单
  • 【练习】PAT 乙 1061 判断题
  • p5.js:sound(音乐)可视化,动画显示音频高低变化
  • FastAPI 请求体参数与 Pydantic 模型完全指南:从基础到嵌套模型实战 [特殊字符]
  • 长方形旋转计算最后的外层宽高
  • JAVA实战开源项目:大学城水电管理系统(Vue+SpringBoot) 附源码
  • spring 和JVM之间关系
  • 【AI论文】GEN3C: 基于3D信息的全球一致视频生成技术,实现精确相机控制
  • Ubuntu 下 Docker 企业级运维指南:核心命令与最佳实践深度解析20250309
  • 51单片机Proteus仿真速成教程——P1-软件与配置+Proteus绘制51单片机最小系统+新建程序模版
  • 十二、Redis Cluster(集群)详解:原理、搭建、数据分片与读写分离
  • 查看电脑信息
  • C++:入门详解(关于C与C++基本差别)
  • [css教程]2025系统全面css教程(四)
  • 【redis】数据类型之geo
  • vs code 设置字体颜色