搜索二叉树的概念及使用

一、二叉搜索树的核心概念与性质

二叉搜索树,亦称二叉排序树,它是一棵具有严格顺序约束的二叉树。其定义遵循递归规则:一棵树要么为空,要么满足以下三条性质:

  • 左子树约束 :若左子树不为空,则左子树上所有 节点的值均小于或等于根节点的值。

  • 右子树约束 :若右子树不为空,则右子树上所有 节点的值均大于或等于根节点的值。

  • 递归性质:其左子树和右子树本身也必须各自是一棵二叉搜索树。

关于重复值的处理策略

二叉搜索树对相等值的插入策略是灵活的,取决于具体应用场景。

  • 唯一键场景 :不允许插入相等的值。对应 C++ STL 中的 std::mapstd::set,它们依赖红黑树实现,保证了键的唯一性。

  • 多重键场景 :允许插入相等的值。对应 C++ STL 中的 std::multimapstd::multiset,允许同一键值多次出现。

二、二叉搜索树的性能深度剖析

二叉搜索树的查找、插入和删除效率与树的高度(h) 直接相关,而高度又取决于数据的插入顺序。

  • 最优情况

    当数据分布均匀,构建出的树形结构为完全二叉树(或接近完全二叉树)时,树的高度约为 h=log⁡2Nh=log2​N。

    • 时间复杂度:O(log⁡N)O(logN)
  • 最差情况

    当数据以近乎有序的顺序(如严格递增或递减)插入时,二叉搜索树会严重失衡,退化为单支树(形似链表)。

    • 树的高度:h=Nh=N

    • 时间复杂度:O(N)O(N)(此时查找效率与遍历链表无异)

  • 综合评价

    综合最好与最坏情况,普通二叉搜索树的增删查改平均时间复杂度通常表述为 O(N),因为最坏情况下的线性退化是不可忽视的风险。

三、为什么需要平衡二叉搜索树?

显然,O(N)O(N) 的效率在处理海量内存数据时是无法接受的。对比传统的二分查找算法,两者之间的权衡更能体现平衡树的独特价值:

对比项 二分查找(有序数组) 普通二叉搜索树 平衡二叉搜索树
查找效率 O(log⁡N)O(logN) O(N)O(N) O(log⁡N)O(logN)
插入/删除 O(N)O(N) (需挪动数据) O(N)O(N) (可能退化为链表) O(log⁡N)O(logN)
存储要求 必须支持随机访问且连续存储 链式存储,内存离散 链式存储,内存离散

结论与展望

二分查找虽查找快,但插入删除代价高昂;普通 BST 虽插入删除灵活,但查找稳定性差。为了兼顾 高效的查找高效的动态修改(插入/删除) ,我们需要一种能够自我调节、防止退化的特殊 BST------平衡二叉搜索树。后续我们将深入探讨 AVL 树和红黑树,它们是解决这一痛点、支撑起现代软件底层数据存储的关键技术。

四、二叉搜索树核心操作

1. 插入操作

插入新结点的过程如下:

  • 若树为空,则直接创建新结点作为根结点。

  • 若树不为空,则从根结点开始:

    • 若插入值小于当前结点值,则向左子树移动;

    • 若插入值大于当前结点值,则向右子树移动;

    • 若支持插入相等值,可以统一规定向左或向右(保持逻辑一致性),直到找到空位插入。

cpp 复制代码
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

依次插入后得到的 BST 结构如下:

cpp 复制代码
        8
       / \
      3   10
     / \    \
    1   6    14
       / \   /
      4   7 13

C++ 实现(插入):

cpp 复制代码
Node* insert(Node* root, int val) {
    if (!root) return new Node(val);
    if (val < root->val)
        root->left = insert(root->left, val);
    else if (val > root->val)
        root->right = insert(root->right, val);
    // 若支持相等值,可在此处处理
    return root;
}

2. 查找操作

查找是否包含某个值:

  • 从根开始,若等于当前结点则返回;

  • 小于则向左,大于则向右;

  • 若走到空结点,则不存在。

cpp 复制代码
bool search(Node* root, int val) {
    if (!root) return false;
    if (val == root->val) return true;
    if (val < root->val)
        return search(root->left, val);
    else
        return search(root->right, val);
}

3. 删除操作(重点)

删除操作相对复杂,需分三种情况:

  1. 叶子结点:直接删除。

  2. 只有一个子结点:用子结点替换当前结点。

  3. 有两个子结点

    • 找到右子树中的最小结点(或左子树中的最大结点);

    • 用该结点值替换当前结点值;

    • 递归删除那个替换结点。

cpp 复制代码
Node* remove(Node* root, int val) {
    if (!root) return nullptr;
    if (val < root->val)
        root->left = remove(root->left, val);
    else if (val > root->val)
        root->right = remove(root->right, val);
    else {
        // 找到要删除的结点
        if (!root->left) {
            Node* rightChild = root->right;
            delete root;
            return rightChild;
        }
        if (!root->right) {
            Node* leftChild = root->left;
            delete root;
            return leftChild;
        }
        // 有两个孩子:找右子树最小结点
        Node* minNode = findMin(root->right);
        root->val = minNode->val;
        root->right = remove(root->right, minNode->val);
    }
    return root;
}
相关推荐
唐青枫16 小时前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马17 小时前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端
用户37215742613517 小时前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
用户37215742613517 小时前
Java 打印 Word 文档:从基础打印到高级设置
java
用户3521802454751 天前
当 Prompt 学会"热更新":Spring Boot × Nacos3 AI 实战
java·spring boot·ai编程
东坡白菜2 天前
破局全栈:一个前端开发的Java入门实战记录(1)
java·全栈
唐青枫2 天前
Java Tomcat 实战指南:从 Servlet 容器到 Spring Boot 部署
java
wsaaaqqq2 天前
roudan:自由选择实体、灵活操作数据、快速写入数据库的 Java 框架
java
plainGeekDev2 天前
null 判断 → Kotlin 可空类型
android·java·kotlin
糖拌西瓜皮2 天前
Java开发者视角:深入理解Node.js异步编程模型
java·后端·node.js