Lecture 13-2

本文详细介绍了二叉搜索树(BST)的概念,包括其定义、功能以及与堆的对比。讨论了BST的基本操作,如查找、插入和删除,并提供了相应的C++实现。此外,还探讨了保持BST平衡的重要性以及几种平衡策略,如AVL树。文章最后指出,所有操作的时间复杂度取决于树的平均深度,强调了维持树平衡的必要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

绪论

介绍完堆,哈夫曼编码之后,BST它来咯hhhh。

(我以为他会开一个单章而不是作为Lecture 13的后半部分介绍的,毕竟比起哈夫曼树,我觉得BST和堆的联系还稍微大一点)。


Definition

之前在介绍堆的时候,有提到过堆对标的是BST。堆的定义是对于所有的分支节点,子节点的值大于根结点的值(为了说明方便这里用数值来代替优先级),BST的定义是对于所有的分支节点,左子树上节点的值小于根结点的值小于右子树上节点的值。

默认BST中不包含值相同的元素。

从定义上来看,BST左右子树上节点与父节点的关系比堆结构左右子节点与父节点的关系更加复杂。

功能上来看,堆能够实现的功能是始终能够访问和删除优先级最高的结点以及添加结点,BST能够实现的功能更加灵活和复杂,可以访问和删除任意优先级的结点,访问的索引可以是优先级对应的数值,可以是优先级,BST结构中的元素始终保持两两之间有序。

在这里插入图片描述

例子如下:

在这里插入图片描述


Implementation

在定义中提到,BST结构中的元素始终保持两两之间有序,元素集合从某种角度可以看作一个有序的序列,对于一个有序的需要实现地操作就是“增删改查”。

增和删作为修改的两个部分,一般只有细微的区别需要进行调整,大多步骤都是对称的。

改直接修改一个结点对应的值,相当于在那个位置删除了原本的结点,然后添加了一个新的结点,改实际和增删是一样的,我个人一般将这三个操作都看作对整体结构的修改。

查是基本操作中最为重要的,因为查询是所有操作的前提,修改等操作都需要首先通过查询来确切进行操作的位置,所以基本操作这里我们首先从查询开始介绍。

类模板如下:

在这里插入图片描述

Inherited Member Functions

对叶子种类的判断,size和height的属性的求解,返回结点的值,返回左(右)子结点指针都是基于一般二叉树原本就存在的操作。BST不改变这些操作的本质,直接继承即可。

在这里插入图片描述

reinterpret_cast是强制转化类型的意思,返回子节点指针在继承的基础上需要将返回的二叉树类型指针强制转换成返回BST类指针。

在这里插入图片描述

Find()

查询分为按照优先级和按照具体值的两种查询:按照优先级查询,就是需要你查询出优先级第k高的结点;按照具体值查询,就是需要你查询出值为给定值的结点。Find()是按具体值查询。

我们知道BST左子树上结点的值小于根结点的值小于右子树上结点的值,按值查询的过程根据BST的结构会分成三种情况——当前根结点的值等于查询的值,当前根结点的值小于查询的值,当前根结点的值大于查询的值。

如果是等于的话,查找成功,直接返回该节点的指针即可。如果小于,说明需要查询的结点,如果存在则出现在左子树上,递归到查询左子树,大于同理。

每次递归可能从当前结点进入到下一层的子节点,时间复杂度为BST的高度。

在这里插入图片描述

Finding the kth Object

Finding the kth Object指的就是按照优先级进行查找,k是优先级。课件上将两种查询方式分割开,认定按照优先级查找是基本操作之外“另外相关的操作”,我个人不是认为两种查询是只是基于BST的不同定义,实质是一样的:

在BST中进行按值查询基于的是BST的最基本定义,BST最基本的定义是左子树上结点的值小于根结点的值小于右子树上结点的值,按值查询根据定义每次分割成在左子树上按值查询和右子树上按值查询。

在BST中进行按优先级的查询基于的是BST功能方面的定义,BST结构的功能是将结构中的所有元素构成有序的情况,假设左子树的结点个数有a个,右子树的结点个数有b个。那么左子树存储的是优先级为1~a的元素,右子树存储的是优先级为a+2~a+b+1的元素,左子树存储的结点和右子树存储的结点都是有序的。

我们在a+b+1个元素中查询第k个元素,根据左子树和右子树的size确定出需要查询的结点在左子树,右子树,根还是不存在,递归的过程和按值查询是一样的(根据优先级查询左子树,右子树还是根)。

两种查询实质都是根据具体值,优先级(子树结点个数)进行的一种二分查找,只是往往认为size是BST(二叉树)结构确定之后才能计算得到的属性(我个人认为从逻辑上两者是没有先后的)。

事实上从优先级的定义来想,考虑到优先级对应的值和优先级存在一一对应的映射,两种查找是完全一样的(我的语文学习的不好hhhh,意会就好)。

代码如下:

在这里插入图片描述

Front(),Back()

front(),back(),还有next(),previous(),都是查找功能中比较常见的几种特殊情况。用按值查找和按优先级查找也能处理(next(),previous()有限定条件hhhh,所以不多做介绍),不过不如单独拿出来分析的效率高。

front和back分别代表查询优先级最高和最低的结点,也就是at(1)和at(n)。但是根据BST的定义我们可以知道:值最小的结点一定不存在左子节点和来自左边的父节点,值最大的结点一定不存在右子节点和来自右边的父节点。也就是说,优先级最高和最低的结点,左边和右边没有别的结点,分别为从根结点开始向左和向右能够到达的最左和最右的结点。

front():

在这里插入图片描述

back():

在这里插入图片描述

Insert()

insert()是插入一个新的元素,按值插入(没有按优先级插入)和按值查询的思路是基本一致的,根据结点的值分割出三种情况:根结点的值小于插入的值,递归到右子树进行插入;根结点的值大于插入的值,递归到左子树进行插入;根节点的值等于插入的值,插入失败(BST中规定没有值重复的元素)。

在这里插入图片描述

插入的结束标志为递归到树最下层的结点,在空缺的叶子节点上进行插入。

在这里插入图片描述

在这里插入图片描述

插入和查询基本是一样的,因为插入的位置是叶子节点,按照查询到的位置进行插入不会波坏BST的结构。

Erase()

删除操作,首先通过按值查询查询出被删除的结点。如果被删除的结点是一个分支结点,删除后的树状存储结构就会变成若干个BST组成的森林,BST的结构被破坏。

在这里插入图片描述
处理方案就是将几个BST转化回一个独立的BST。

如果有一个子树为空,我们将另一边存在的子树的根节点直接补充到空缺出来的位置。如果被删除的结点左(右)子树不存在,存在的右(左)子树的根节点仍然满足被删除的结点与其父节点的相对关系(因为存在的子树集合相同)。

如果两个子树都不为空,两个子树上的结点和被删除的结点属于同一个子树集合,与被删除结点与其父节点的相对关系相同,所以我们需要做的还是在两个子树中找到一个结点来填充被删除结点的空缺。

左子树上结点的值小于被删除结点的值小于右子树上结点的值,假设这个结点是左子树中找到的,那么这个结点需要大于左子树上其余结点的值,也就是左子树上的最大结点(front),,同时这个结点因为属于原本的左子树集合,值也小于右子树上的结点。同理,右子树上找到填充的结点也是右子树上值最小的结点。

所以,对于两个子树都存在的策略,就是寻找左子树的back或者右子树的front填充到根结点的位置,然后删去front(back)。在这里插入图片描述


总结

BST所有操作的复杂度和堆一样基本取决于树的平均深度,所以同样需要尽可能地避免退化成链的情况,也就是尽可能地保持平衡。

保持平衡的方案一般有:

Treap树
B+树
Splay树
AVL树
红黑树
······

下一章节介绍的就是AVL树。

至于BST的部分,和课本上的基本上完全一样,不过是用C++实现的,使用了很多C++类所有的继承的手段来方便减少编写的复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值