数据结构——四十二、二叉排序树(王道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

七.知识回顾与重要考点

结语

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

相关推荐
Funny_AI_LAB4 小时前
李飞飞联合杨立昆发表最新论文:超感知AI模型从视频中“看懂”并“预见”三维世界
人工智能·算法·语言模型·音视频
wdfk_prog7 小时前
[Linux]学习笔记系列 -- [kernel]kthread
linux·笔记·学习
RTC老炮7 小时前
webrtc降噪-PriorSignalModelEstimator类源码分析与算法原理
算法·webrtc
草莓火锅9 小时前
用c++使输入的数字各个位上数字反转得到一个新数
开发语言·c++·算法
散峰而望9 小时前
C/C++输入输出初级(一) (算法竞赛)
c语言·开发语言·c++·算法·github
摇滚侠9 小时前
StreamAPI,取出list中的name属性,返回一个新list
数据结构·list
Kuo-Teng9 小时前
LeetCode 160: Intersection of Two Linked Lists
java·算法·leetcode·职场和发展
鸽子一号9 小时前
c#笔记之面向对象
笔记
fie88899 小时前
基于MATLAB的狼群算法实现
开发语言·算法·matlab