二叉查找树( BST)
一、概念
1、简述
一种能够将链表插入的灵活性和有序数组查找的高效性结合起来的符号表实现。
具体来说,就是使用每个结点含有两个链接(链表中每个结点只含有一个链接)的二叉查找树来高效地实现符号表
2、特点
(1)在二叉查找树中,每个结点还包含了一个键和一个值,键之间也有顺序之分以支持高效的查找。
(2)一棵二叉查找树本质是一棵二叉树,其中每个结点都含有一个 可比较的键(以及相关联的值)且每个结点的键都大于其左子树中的任意结点的键而小于右子树的任意结点的键。
(3)使用二叉查找树的算法的运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。在最好的情况下,一棵含有 N 个结点的树是完全平衡的,每条空链接和根结点的距离都为~ lgN。在最坏的情况下,搜索路径上可能有 N个结点。
(4)一棵二叉查找树代表了一组键(及其相应的值)的集合,而同一个集合可以用多棵不同的二叉查找树表示
(5)在一棵二叉查找树中,所有操作在最坏情况下所需的时间都和树的高度成正比。
二、查找
1、算法
在二叉查找树中查找一个键的递归算法:
如果树是空的,则查找未命中;
如果被查找的键和根结点的键相等,查找命中,
否则我们就(递归地)在适当的子树中继续查找。
如果被查找的键较小就选择左子树,较大则选择右子树
2、特点
(1)只有该结点所表示的子树才会含有和被查找的键相等的结点。
(2)和二分查找中每次迭代之后查找的区间就会减半一样,在二叉查找树中,随着我们不断向下查找,当前结点所表示的子树的大小也在减小(理想情况下是减半,但至少会有一个结点)。
(3)当找到一个含有被查找的键的结点(命中)或者当前子树变为空(未命中)时这个过程才会结束。
(4)从根结点开始,在每个结点中查找的进程都会递归地在它的一个子结点上展开,因此一次查找也就定义了树的一条路径。
(5)对于命中的查找,路径在含有被查找的键的结点处结束。
(6)对于未命中的查找,路径的终点是一个空链接,
三、插入
二叉查找树的另一个更重要的特性就是插入的实现难度和查找差不多。
1、算法
插入之前先查找要插入的键,当查找一个不存在于树中的结点并结束于一条空链接时,将链接指向一个含有要插入的键的新结点
如果树是空的,就返回一个含有该键值对的新结点;
如果被查找的键小于根结点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。
2、特点
(1)使用二叉查找树的算法的运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。
(2)在最好的情况下,一棵含有 N 个结点的树是完全平衡的,每条空链接和根结点的距离都为~ lgN。
(3)在最坏的情况下,搜索路径上可能有 N个结点。
(4)在由 N 个随机键构造的二叉查找树中,查找命中平均所需的比较次数为∼ 2lnN(约1.39lgN)
(5)在由 N 个随机键构造的二叉查找树中插入操作和查找未命中平均所需的比较次数为∼ 2lnN(约 1.39lgN)
(6)二叉查找树得以广泛应用的一个重要原因就是它能够保持键的有序性
四、最大键和最小键
1、最小键
如果根结点的左链接为空,那么一棵二叉查找树中最小的键就是根结点;
如果左链接非空,那么树中的最小键就是左子树中的最小键。
2、最大键
找出最大键的方 法也是类似的,只是变为查找右子树。
五、向上取整和向下取整
1、向下取整 floor
如果给定的键 key 小于二叉查找树的根结点的键,那么小于等于 key 的最大键 floor(key) 一定在根结点的左子树中;
如果给定的键 key 大于二叉查找树的根结点,那么只有当根结点右子树中存在小于等于 key 的结点时,小于等于 key 的最大键才会出现在右子树中,否则根结点就是小于等于 key的最大键。
2、向上取整 ceiling
将"左"变为"右"(同时将小于变为大于)就能够得到向上取整 ceiling() 的算法。
六、选择操作 select
假设我们想找到排名为k的键(即树中正好有k个小于它的键)。
如果左子树中的结点数t大于k,那么我们就继续(递归地)在左子树中查找排名为 k 的键;
如果 t 等于 k,我们就返回根结点中的键;
如果 t 小于 k,我们就(递归地)在右子树中查找排名为( k-t-1) 的键。
七、排名 rank
rank() 是 select() 的逆方法,它会返回给定键的排名。
它的实现和 select() 类似:
如果给定的键和根结点的键相等,我们返回左子树中的结点总数 t;
如果给定的键小于根结点,我们会返回该键在左子树中的排名(递归计算);
如果给定的键大于根结点,我们会返回 t+1(根结点)加上它在右子树中的排名(递归计算)
八、删除最大键和删除最小键
1、删除最小键deleteMin()
递归方法接受一个指向结点的链接,并返回一个指向结点的链接。这样我们就能够方便地改变树的结构,将返回的链接赋给作为参数的链接。
对于 deleteMin(),要不断深入根结点的左子树中直至遇见一个空链接,然后将指向该结点的链接指向该结点的右子树
此时已经没有任何链接指向要被删除的结点,因此它会被清理掉。
可以用类似的方式删除任意只有一个子结点(或者没有子结点)的结点
2、删除最大键deleteMax()
deleteMax()方法的实现和 deleteMin() 完全类似。
九、删除
若从二叉搜素树中删除节点x, 在删除结点 x 后需要用它的后继结点填补它的位置。
因为 x 有一个右子结点,因此它的后继结点就是其右子树中的最小结点。
因为 x.key 和它的后继结点的键之间不存在其他的键,所以替换后仍然能够保证树的有序性,
将 x 替换为它的后继结点的步骤:
(1) 将指向即将被删除的结点的链接保存为 t;
(2) 将 x 指向它的后继结点 min(t.right);
(3) 将 x 的右链接(原本指向一棵所有结点都大于 x.key 的二叉查找树)指向 deleteMin(t.right),也就是在删除后所有结点仍然都大于 x.key 的子二叉查找树;
(4) 将 x 的左链接(本为空)设为 t.left(其下所有的键都小于被删除的结点和它的后继结点)。
十、范围查找
1、中序遍历
遍历二叉查找树的基本方法,叫做中序遍历
首先要遍历的是根结点的左子树中的所有键(根据二叉查找树的定义它们应该都小于根结点的键),
然后是根结点的键,
最后遍历根结点的右子树中的所有键(根据二叉查找树的定义它们应该都大于根结点的键)
2、范围查找
给定两个参数,并将两参数确定范围内的键返回给用例的keys()方法
修改一下中序遍历逻辑,将所有落在给定范围以内的键加入一个队列 Queue 并跳过那些不可能含有所查找键的子树