数据结构——四十二、二叉排序树(王道408)

文章目录

前言

本文介绍了二叉排序树(BST)的定义、查找、插入、构造和删除操作。二叉排序树是一种左子树结点值小于根结点值,右子树结点值大于根结点值的特殊二叉树,其中序遍历可得到递增序列。查找操作通过比较关键字值在左右子树中递归搜索。插入操作根据关键字大小决定插入位置。构造BST即不断插入新结点的过程。删除操作需分三种情况处理:删除叶子结点、仅有一棵子树的结点或两棵子树的结点。对于同时有左右子树的结点,可采用直接后继代替法处理。文章通过图示和代码示例详细说明了各操作的实现过程和应用场景。

一.二叉排序树的定义

  • 二叉排序树,又称二叉查找树(BST). Binary Search Tree)
  • 一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
    左子树上所有结点的关键字均小于根结点的关键字;
    右子树上所有结点的关键字均大于根结点的关键字。
    左子树和右子树又各是一棵二叉排序树。

左子树结点值 < 根结点值 < 右子树结点值

进行中序遍历,可以得到一个递增的有序序列
二叉排序树可用于元素的有序组织、搜索

二.二叉排序树的查找

1.思路

  • 若树非空,目标值与根结点的值比较:
  • 若相等,则查找成功;
  • 若小于根结点,则在左子树上查找,否则在右子树上查找。
  • 查找成功,返回结点指针;查找失败返回NULL

2.代码

c 复制代码
//二叉排序树结点
typedef struct BSTNode{
	int key;
	struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;

//在二叉排序树中查找值为 key 的结点
BSTNode *BST_Search(BSTree T,int key){
	while(T!=NULL&&key!=T->key){//若树空或等于根结点值,则结束循环
		if(key<T->key) T=T->lchild;//小于,则在左子树上查找
		else T=T->rchild;//大于,则在右子树上查找
	}
	return T;
}

//在二叉排序树中查找值为 key 的结点(递归实现)
BSTNode *BSTSearch(BSTree T,int key){
	if (T==NULL)
		return NULL;//查找失败
	if (key==T->key)
		return T;//查找成功
	else if (key<T->key)
		return BSTSearch(T->lchild, key); //在左子树中找
	else
		return BSTSearch(T->rchild, key); //在右子树中找
}
  • 循环迭代算法空间复杂度:O(1)
  • 递归空间复杂度:O(h),h为树的高度

3.使用过程

  • 查找关键字为30的结点
  1. 从根节点出发,如果当前访问的节点是一个非空节点,并且我们要找到那个值是要大于当前节点的,那么根据排序数的特性,我们要找的点肯定是在右子树中,所以我们可以让指针往他的右孩子方向走
  2. 现在我们要查找的节点30要比50更小,那肯定是在50这个节点的左子树上,所以就可以往左走
  3. 接下来的操作类似,结果如下:
  4. 假设此时T指向的是NULL,说明查找失败

三.二叉排序树的插入

1.思路

  • 若原二叉排序树为空,则直接插入结点;否则,若关键字k小于根结点值,则插入到左子树,若关键字k大于根结点值,则插入到右子树

2.代码

c 复制代码
//在二叉排序树插入关键字为k的新结点(递归实现)
int BST_INSERT(BSTree&T, int k){
	if(T==NULL){	//原树为空,新插入的结点为根结点
		T=(BSTree)malloc(sizeof(BSTNode));
		T->key=k;
		T->lchild=T->rchild=NULL;
		return 1;	//返回1,插入成功
	}
	else if(k==T->key)	//树中存在相同关键字的结点,插入失败
		return 0;
	else if(k<T->key)	//插入到T的左子树
		return BST_INSERT(T->lchild,k);
	else	//插入到T的右子树
		return BST_INSERT(T->rchild,k);
}

// 非递归插入新结点到二叉排序树
bool InsertBST(BSTree *T, int key) {
    // 创建新结点
    BSTNode *newNode = (BSTNode *)malloc(sizeof(BSTNode));
    if (newNode == NULL) {
        return false; // 内存分配失败
    }
    newNode->key = key;
    newNode->lchild = newNode->rchild = NULL;
    
    // 如果树为空,新结点作为根结点
    if (*T == NULL) {
        *T = newNode;
        return true;
    }
    
    BSTNode *current = *T;
    BSTNode *parent = NULL;
    
    // 寻找插入位置
    while (current != NULL) {
        parent = current;
        
        if (key == current->key) {
            free(newNode); // 键值已存在,释放新结点
            return false;  // 插入失败
        } else if (key < current->key) {
            current = current->lchild; // 在左子树中查找
        } else {
            current = current->rchild; // 在右子树中查找
        }
    }
    
    // 插入新结点
    if (key < parent->key) {
        parent->lchild = newNode;
    } else {
        parent->rchild = newNode;
    }
    
    return true;
}
  • 递归方式最坏空间复杂度O(h),h为树的高度
  • 非递归方式空间复杂度为O(1)

3.使用过程

  • 插入关键字为12的结点
  1. 那么从根节点出发,12比19更小,那显然应该插入到左子树
  2. 接下来12小于13,所以还应该插入到它的左子树当中
  3. 那现在12大于11,所以应该插入到当前节点的右子树当中
  4. 如果二叉树中已经有关键字为12的元素,则插入失败

新插入的结点一定是叶子节点

四.二叉排序树的构造

1.思路

  • 就是不断插入新结点的过程

2.代码

c 复制代码
//按照 str[]中的关键字序列建立二叉排序树
void Creat_BST(BST&T, int str[], int n){
	T=NULL;	//初始时T为空树
	int i=0;
	while(i<n){//依次将每个关键字插入到二叉排序树中
		BST Insert(T,str[i]);
		i++;
	}
}

3.使用过程

  • 例1: 按照序列str={50,66,60,26,21,30,70,68}建立BST
  1. 首先插入的是50
  2. 接下来66>50,插入到右孩子
  3. 60<60应该插在66的左边
  4. 然后是26<50,插在50的左孩子
  5. 接下来操作一样,最终结果如下:
  • 例2:按照序列str={50,26,21,30,66,60,70,68}建立BST
  • 也是一样的,这里就不展示过程了,直接看结果

不同的关键字序列可能得到同款二叉排序树

五.二叉排序树的删除

1.思路

  • 先搜索找到目标结点:
    ①若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。


    ②若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。


    ③若结点z有左、右两棵子树,
    1. 第一种方案(直接后继代替法)我们可以从当前删除的节点,它的右子树当中找到值最小的节点(右子树中中序遍历的第一个结点,在树上面看就时左子树中最左下的那个节点),用那个节点来替代当前被删除的节点
    2. 由于p所指向的结点一定是最左下的结点,因此其一定没有左子树,这样就转换成了我们之前的第二/一种情况

    3. 第二种方案(直接前驱替代法),找到当前节点它的左子树当中最大的那个值(左子树中最右下的结点 ),用左子树当中最大的值来替代当前被删除的节点.和之前类似的,p所指向的结点一定没有右子树,则可以转换为之前的第一/二种情况

六.查找效率分析

1.查找成功

1.计算平均查找长度(ASL)

  • A S L = ( 1 ∗ 1 + 2 ∗ 2 + 3 ∗ 4 + 4 ∗ 1 ) / 8 = 2.625 ASL=(1*1+2*2+3*4+4*1)/8=2.625 ASL=(1∗1+2∗2+3∗4+4∗1)/8=2.625
  • A S L = ( 1 ∗ 1 + 2 ∗ 2 + 3 ∗ 1 + 4 ∗ 1 + 5 ∗ 1 + 6 ∗ 1 + 7 ∗ 1 ) / 8 = 3.75 ASL = (1*1 + 2*2 + 3*1 + 4*1 + 5*1 + 6*1+ 7*1)/8 = 3.75 ASL=(1∗1+2∗2+3∗1+4∗1+5∗1+6∗1+7∗1)/8=3.75

2.查找的时间复杂度

  • 最好情况:n个结点的二叉树最小高度为 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor\log_{2}n\rfloor+1 ⌊log2n⌋+1。平均查找长度= O ( l o g 2 n ) O(log_{2}n) O(log2n)
  • 最坏情况:每个结点只有一个分支,树高h=结点数n。平均查找长度=O(n)

2.查找失败

1.计算平均查找长度(ASL)

  • A S L = ( 3 ∗ 7 + 4 ∗ 2 ) / 9 = 3.22 ASL=(3*7+4*2)/9=3.22 ASL=(3∗7+4∗2)/9=3.22
  • A S L = ( 2 ∗ 3 + 3 + 4 + 5 + 6 + 7 ∗ 2 ) / 9 = 4.22 ASL=(2*3+3+4+5+6+7*2)/9=4.22 ASL=(2∗3+3+4+5+6+7∗2)/9=4.22

七.知识回顾与重要考点

结语

二更😉
如果想查看更多章节,请点击:一、数据结构专栏导航页

相关推荐
京东零售技术10 分钟前
下一代 Lakehouse 智能未来新引擎 | Apache Hudi Meetup亚洲站活动回顾
算法
京东零售技术12 分钟前
NeurIPS 2025 | TANDEM:基于双层优化的数据配比学习方法
后端·算法
zmzb010321 分钟前
C++课后习题训练记录Day42
开发语言·c++·算法
CoovallyAIHub23 分钟前
MAR-YOLOv9:革新农业检测,YOLOv9的“低调”逆袭
深度学习·算法·计算机视觉
dragoooon3427 分钟前
[优选算法专题十.哈希表 ——NO.55~57 两数之和、判定是否互为字符重排、存在重复元素]
数据结构·散列表
Mr Lee_29 分钟前
Smali 文件生成dex装箱算法整合
开发语言·python·算法
自不量力的A同学31 分钟前
FreeFileSync 14.6 发布
笔记
可可苏饼干33 分钟前
ELK(Elastic Stack)日志采集与分析
linux·运维·笔记·elk
LDG_AGI35 分钟前
【推荐系统】深度学习训练框架(十三):模型输入——《特征索引》与《特征向量》的边界
人工智能·pytorch·分布式·深度学习·算法·机器学习
CoovallyAIHub36 分钟前
如何让SAM3在医学图像上比专用模型还强?一个轻量Adapter如何让它“秒变”专家?
深度学习·算法·计算机视觉