定义
一棵二叉查找树(BST)是一棵二叉树,其中每个结点都含有一个Comparable的键(以及相关联的值)且每个结点的键都大于其左子树中的任意结点的键而小于右子树的任意结点的键。
基本实现
数据表示
和链表一样,我们嵌套定义了一个私有类来表示二叉查找树上的一个结点。每个结点都含有一个键、一个值、一条左链接、一条右链接和一个结点计数器。左链接指向一棵由小于该结点的所有键组成的二叉查找树,右链接指向一棵大于该结点的所有键组成的二叉查找树。并且我们能保证下面的公式对于二叉树中的任意结点x总是成立的:
size(x) = size(x.left) + size(x.right) + 1
查找
在二叉查找树中查找一个键的递归算法:如果树是空的,则查找未命中;如果被查找的键和根节点的键相等,查找命中,否则我们就递归地在适当的子树中继续查找。如果被查找的键较小就选择左子树,较大则选择右子树。对于命中的查找,路径在含有被查找的键的结点处结束。对于未命中的查找,路径的终点是一个空链接。
插入
插入的实现逻辑和递归查找很相似:如果树是空的,就返回一个含有该键值对的新结点;如果被查找的键小于根节点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。
实现
分析
使用二叉查找树的算法的运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。在最好的情况下,一棵含有N个结点的树是完全平衡的,每条空链接和根结点的距离都为~lgN。在最坏的情况下,搜索路径上可能有N个结点。但在一般情况下树的形状和最好情况更接近。
有序性相关的方法与删除操作
最大键和最小键
如果根结点的左链接为空,那么一棵二叉查找树中最小的键就是根结点;如果左链接非空,那么树中的最小键就是左子树中的最小键。找出最大键的方法也是类似的,只是变为查找右子树而已。
向上取整和向下取整
如果给定的键key小于二叉查找树的根结点的键,那么小于等于key的最大键floor(key)一定在根结点的左子树中;如果给定的键key大于二叉查找树的根结点,那么只有当根结点右子树中存在小于等于key的结点时,小于等于key的最大键才会出现在右子树中,否则根结点就是小于等于key的最大键。同理可以得到ceiling()的算法。
选择操作
我们在二叉查找树的每个结点中维护的子树结点计数器变量N就是用来支持此操作的。假设我们想找到排名为k的键:
- 如果左子树中的结点树t大于k,那么我们就继续在左子树中查找排名为k的键;
- 如果t等于k,我们就返回根结点中的键;
- 如果t小于k,我们就在右子树中查找排名为(k - t - 1)的键。
排名
rank()是select()的逆方法,它会返回给定键的排名。它的实现和select()类似。
删除最大键和删除最小键
要删除最小键,我们不断深入根结点的左子树中直至遇见一个空链接,然后将指向该结点的链接指向该结点的右子树。删除最大键类似。
删除操作
在删除结点x后用它的后继结点填补它的位置。因为x有一个右子结点,因此它的后继结点就是其右子树中的最小结点。这样替换仍然能够保证树的有序性,因为x.key和它的后继结点的键之间不存在其他的键。
范围查找
使用中序遍历,先打印出根结点的左子树中的所有键,然后打印出根结点的键,最后打印出根结点的右子树中的所有键。