数据结构——二叉搜索树

二叉搜索树

在动态查找表中,数据元素会频繁地插入和删除,此时顺序查找效率太低,折半查找又依赖全表有序------二叉搜索树(又称二叉排序树)恰好解决了这一问题。它是一种特殊的二叉树,通过定义节点值的大小关系,让查找、插入、删除操作都能在树的高度范围内完成,兼顾了动态性和效率。无论是通讯录的动态管理,还是数据库的索引结构,二叉搜索树都有着广泛的应用。

1. 二叉搜索树的定义与结构

二叉搜索树是一种空树,或者是具有以下特性的二叉树:

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

这种特性保证了二叉搜索树的"有序性"------从根节点出发,左子树始终是"小值区域",右子树始终是"大值区域",就像一本按规则排列的字典,每个节点都是其左子树的"最大值"和右子树的"最小值"。

我们用一个具体的二叉搜索树示例(节点值为整数)来直观理解其结构,用mermaid绘制如下:
60 40 70 20 50 10 30 65 80

在这棵树中:

  • 根节点60的左子树所有节点(40、20、50、10、30)均小于60,右子树所有节点(70、65、80)均大于60;
  • 节点40的左子树(20、10、30)均小于40,右子树(50)大于40;
  • 每个子树都满足同样的规则,例如节点20的左子树10小于20,右子树30大于20。
2. 二叉搜索树的查找操作

二叉搜索树的查找过程利用了其"左小右大"的特性,类似折半查找的"缩小范围"思想,但通过树的分支实现。具体步骤如下:

  • 从根节点开始,将目标值target与当前节点的值比较;
  • target等于当前节点的值,查找成功,返回当前节点;
  • target小于当前节点的值,说明目标可能在左子树,继续在左子树中查找;
  • target大于当前节点的值,说明目标可能在右子树,继续在右子树中查找;
  • 若当前节点为空(即到达叶子节点的子树),则查找失败,返回空指针。

以之前的二叉搜索树为例,查找target=50的过程用mermaid绘制如下:

flowchart LR start[开始] --> A[当前节点=60,50 < 60 → 左子树] A --> B[当前节点=40,50 > 40 → 右子树] B --> C[当前节点=50,50 == 50 → 查找成功]

查找target=25的过程则是:

从根节点60→左子树40→左子树20→右子树30(25 < 30)→左子树为空,最终查找失败。

二叉搜索树查找的C语言实现(迭代版本,避免递归栈开销)如下:

c 复制代码
// 二叉搜索树节点结构
typedef struct BSTNode {
    int key; // 关键字
    struct BSTNode *lchild, *rchild; // 左右孩子
} BSTNode;

// 查找关键字为target的节点,返回节点指针(失败返回NULL)
BSTNode* BST_Search(BSTNode *root, int target) {
    while (root != NULL) { // 节点非空则继续查找
        if (root->key == target) return root; // 找到目标,返回节点
        else if (target < root->key) root = root->lchild; // 左子树查找
        else root = root->rchild; // 右子树查找
    }
    return NULL; // 查找失败
}

代码说明:通过循环遍历树,根据目标值与当前节点值的大小关系,不断转向左或右子树,直到找到节点或遍历至空树,时间复杂度取决于树的高度。

3. 二叉搜索树的插入操作

插入操作的核心是"找到合适的位置并保持二叉搜索树的特性",新节点始终作为叶子节点插入(不破坏已有结构)。具体步骤如下:

  • 若树为空,直接将新节点作为根节点;
  • 若树非空,从根节点开始,类似查找过程:
    1)比较新节点值key与当前节点值:
    • key小于当前节点值,且当前节点左子树为空,则将新节点插入为左孩子;
    • key大于当前节点值,且当前节点右子树为空,则将新节点插入为右孩子;
    • 若子树非空,则继续在对应子树中查找插入位置(注意:二叉搜索树通常不允许重复值,若key与当前节点值相等,可视为插入失败或覆盖值)。

我们以"依次插入60、40、70、20、50、10、30、65、80"为例,展示二叉搜索树的构造过程(即上述示例树的形成过程),用mermaid分步绘制如下:
插入60 插入40 插入70 插入20 插入50 插入10 插入30 插入65 插入80 树为空,60作为根节点 40 < 60,插入60左子树 70 > 60,插入60右子树 20 < 40,插入40左子树 50 > 40,插入40右子树 10 < 20,插入20左子树 30 > 20,插入20右子树 65 < 70,插入70左子树 80 > 70,插入70右子树 插入60 插入40 插入70 插入20 插入50 插入10 插入30 插入65 插入80

每一步插入都遵循"左小右大"的规则,最终形成的树保持了二叉搜索树的特性。插入操作的C语言实现如下:

c 复制代码
// 插入关键字为key的新节点,返回根节点(处理空树情况)
BSTNode* BST_Insert(BSTNode *root, int key) {
    if (root == NULL) { // 树为空,创建新节点作为根
        root = (BSTNode*)malloc(sizeof(BSTNode));
        root->key = key;
        root->lchild = root->rchild = NULL;
        return root;
    }
    if (key < root->key) // 插入左子树
        root->lchild = BST_Insert(root->lchild, key);
    else if (key > root->key) // 插入右子树
        root->rchild = BST_Insert(root->rchild, key);
    // 若key相等,此处不处理(视为不插入重复值)
    return root;
}

代码说明:采用递归实现,当找到空位置时创建新节点,否则递归向左或右子树插入,确保新节点作为叶子节点插入,维持树的特性。

4. 二叉搜索树的删除操作

删除操作是二叉搜索树中最复杂的操作,需要在删除节点后仍保持"左小右大"的特性。根据被删除节点的子树情况,分为三种情况:

(1)被删除节点是叶子节点(无左、右子树)

直接删除该节点,将其双亲节点的对应指针(左或右孩子)设为NULL即可。例如,在示例树中删除节点10(叶子节点),只需将节点20的左指针设为NULL,树的其他结构不变。

(2)被删除节点只有一棵子树(左子树或右子树)

用子树替代被删除节点的位置:若节点只有左子树,则将左子树连接到其双亲节点的对应指针;若只有右子树,则将右子树连接过去。例如,删除节点20(左子树10,右子树30),假设节点20是节点40的左孩子,则将节点40的左指针指向节点20的左子树(10)或右子树(30)------实际中需判断哪棵子树存在,此处节点20左右子树均存在,不适用该情况,仅举例说明逻辑。

(3)被删除节点有两棵子树(左、右子树均非空)

这种情况最复杂,需找到"替代节点"来填补被删除节点的位置,替代节点需满足:值大于左子树所有节点,且小于右子树所有节点(即保持树的有序性)。通常选择"中序前驱"或"中序后继"作为替代节点:

  • 中序前驱:被删除节点左子树中值最大的节点(左子树的最右节点);
  • 中序后继:被删除节点右子树中值最小的节点(右子树的最左节点)。

替代后,删除该替代节点(替代节点必为叶子节点或只有一棵子树,可用前两种情况处理)。

以示例树中删除根节点60(有左右子树)为例,步骤如下:

  • 找中序后继:右子树70的最左节点65(右子树中值最小);
  • 用65替代60的位置,此时根节点变为65;
  • 删除原节点65(它是节点70的左孩子,且为叶子节点),将70的左指针设为NULL。

删除后的树结构用mermaid绘制如下:
65 40 70 20 50 10 30 NULL 80

删除操作的C语言实现(核心逻辑)如下:

c 复制代码
// 删除关键字为key的节点,返回根节点
BSTNode* BST_Delete(BSTNode *root, int key) {
    if (root == NULL) return NULL; // 树空,无需删除
    if (key < root->key) // 向左子树查找删除
        root->lchild = BST_Delete(root->lchild, key);
    else if (key > root->key) // 向右子树查找删除
        root->rchild = BST_Delete(root->rchild, key);
    else { // 找到待删除节点
        if (root->lchild == NULL) { // 无左子树(含叶子节点)
            BSTNode *temp = root->rchild;
            free(root);
            return temp;
        } else if (root->rchild == NULL) { // 无右子树
            BSTNode *temp = root->lchild;
            free(root);
            return temp;
        }
        // 有两棵子树,找中序后继(右子树最左节点)
        BSTNode *temp = root->rchild;
        while (temp->lchild != NULL) temp = temp->lchild;
        root->key = temp->key; // 用后继值替代
        root->rchild = BST_Delete(root->rchild, temp->key); // 删除后继
    }
    return root;
}

代码说明:递归查找待删除节点,根据子树情况处理:无左子树则用右子树替代,无右子树则用左子树替代,有两棵子树则用中序后继替代并删除后继节点,确保删除后仍为二叉搜索树。

5. 二叉搜索树的中序遍历特性

二叉搜索树的中序遍历(左子树→根节点→右子树)具有特殊意义------遍历结果是一个递增的有序序列。这是由其"左小右大"的特性决定的,例如示例树的中序遍历结果为:10, 20, 30, 40, 50, 60, 65, 70, 80,恰好是递增序列。

这一特性可用于验证二叉搜索树的正确性:若一棵二叉树的中序遍历结果是有序的,则它是二叉搜索树(反之亦然)。同时,中序遍历也为查找"第k小元素""前驱/后继节点"等操作提供了便捷方式。

6. 二叉搜索树的查找效率分析

二叉搜索树的查找、插入、删除操作的时间复杂度均取决于树的高度h,即O(h)。树的高度与节点插入顺序密切相关:

  • 最好情况 :树接近平衡(左右子树高度差较小),此时h ≈ log₂n(n为节点数),时间复杂度为O(logn),与折半查找相当;
  • 最坏情况 :节点按有序序列插入(如10,20,30,40),树退化为单支树(斜树),此时h = n,时间复杂度退化至O(n),与顺序查找相同;
  • 平均情况 :若节点插入顺序随机,树的高度约为logn,平均时间复杂度为O(logn)

例如,插入序列为60,40,70,20,50(随机顺序),树的高度为3(log₂5≈2.32),接近平衡;而插入序列为10,20,30,40,50(递增顺序),树的高度为5,退化为斜树。

这种效率的不稳定性是二叉搜索树的主要缺陷,后续的平衡二叉树、红黑树等结构正是为了解决这一问题,通过维护树的平衡性来保证高效的操作性能。

综上,二叉搜索树通过"左小右大"的特性实现了动态查找表的高效操作,查找、插入、删除均能在树高范围内完成。其核心优势是动态性------无需像折半查找那样依赖全表有序,插入删除只需调整少量指针;但缺点是效率受树的形态影响较大,最坏情况下性能较差。理解二叉搜索树的操作逻辑和特性,是学习更复杂平衡树结构的基础,也为实际应用中选择合适的查找结构提供了依据。

相关推荐
攻城狮CSU5 小时前
类型转换汇总 之C#
java·算法·c#
小老鼠不吃猫6 小时前
C++ STL <algorithm>中泛型算法:查找、排序、修改、统计、生成
c++·算法·排序算法
白杆杆红伞伞6 小时前
01_svm_二分类
算法·支持向量机·分类
isyoungboy6 小时前
使用SVM构建光照鲁棒的颜色分类器:从特征提取到SVM
算法·机器学习·支持向量机
极客数模6 小时前
2025年MathorCup 大数据竞赛明日开赛,注意事项!论文提交规范、模板、承诺书正确使用!2025年第六届MathorCup数学应用挑战赛——大数据竞赛
大数据·python·算法·matlab·图论·比赛推荐
.小小陈.6 小时前
数据结构3:复杂度
c语言·开发语言·数据结构·笔记·学习·算法·visual studio
立志成为大牛的小牛6 小时前
数据结构——二十四、图(王道408)
数据结构·学习·程序人生·考研·算法
TT哇6 小时前
【优先级队列(堆)】2.数据流中的第 K ⼤元素(easy)
算法·1024程序员节
Matlab程序猿小助手7 小时前
【MATLAB源码-第303期】基于matlab的蒲公英优化算法(DO)机器人栅格路径规划,输出做短路径图和适应度曲线.
开发语言·算法·matlab·机器人·kmeans