搜索二叉树的概念及使用

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

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

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

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

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

关于重复值的处理策略

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

  • 唯一键场景 :不允许插入相等的值。对应 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;
}
相关推荐
不知名的老吴2 小时前
R语言4.3.0安装包百度网盘中文版下载与详细安装指南
开发语言·r语言
棉猴2 小时前
python海龟绘图之计算夹角towards()
开发语言·python·turtle·海龟绘图·towards
张人玉2 小时前
SMT 贴片机上位机项目
开发语言·c#
我不是懒洋洋2 小时前
【经典题目】链表OJ(相交链表、环形链表、环形链表II、随机链表的复制)
c语言·开发语言·数据结构·链表·ecmascript·visual studio
被摘下的星星2 小时前
Maven
java·maven
ん贤2 小时前
口述Map
开发语言·面试·golang
悟空码字2 小时前
别再重复造轮子了!SpringBoot对接第三方系统模板,拿来即用
java·spring boot·后端
yaaakaaang2 小时前
十七、迭代器模式
java·迭代器模式
我爱cope2 小时前
【从0开始学设计模式-8| 桥接模式】
java·设计模式·桥接模式