二叉查找树是按二叉树的结构来组织的,如下图所示。每个结点都是一个对象;结点中除了数据key外,还有三个指针p,left,right,分别指向父结点,左儿子,右儿子。
二叉查找树总是满足一下性质:
x是二叉查找树中的一个结点。如果y是x左子树的一个节点,则key[y]<=key[x]。如果y是右子树的一个结点,则key[y]>=key[x]。
根据二叉查找树的性质,可以用一个递归算法按排列顺序输出所有关键字:中序遍历。
//中序遍历
INORDER-TREE-WALK(x)
if x≠NIL
then INORDER-TREE-WALK(left[x])
pinrt key[x]
INORDER-TREE-WALK(right[x])
除此之外,还有前序遍历和后序遍历。显然遍历时间复杂度为O(n)。
对于二叉查找树,最常见的操作是查找某个关键字。树的高度为h,那么这些操作都可以在O(h)时间完成。
给定指向根的指针x和关键字k,返回包含关键字k的指针(如果存在),否则返回NIL。
TREE-SEARCH(x,k)
if n=NIL or k=key[x]
then return x
if k<key[x]
then return TREE-SEARCH(letf[x],k)
else return TREE-SEARCH(right[x],k)
也可以用while循环来代替递归,一般来说非递归版本运行的更快一些
ITERATIVE-TREE-SEARCH(x,k)
while x≠NIL and k≠key[x]
if k<key[x]
then x←letf[x]
else x←right[x]
return x
对于查找最大或最小关键字,更容易。根据二叉查找树的性质,从根开始,一直沿着左孩子找下去,直到遇到NIL为止,找到的就是最小的;一直沿着右孩子一直找下去,直到找到NIL为止,找到的就是最大的。
找最小关键字
TREE-MINIMUM(x)
while left[x]≠NIL
then x←left[x]
return x
找最大关键字
TREE-MAXMUM(x)
while right[x]≠NIL
then x←right[x]
return x
前趋和后继
在中序遍历下,输出的结点是从小到大排列好的。如果各个结点的关键字不相等,那么x其前趋就是刚好小于key[x]那个,其后继是刚好大于key[x]那个。根据二叉查找树的特殊结构,不用做对比,就可以直接找出其前趋和后继结点。
对于x其后继结点,如果right[x]不为空,那么其后继结点为TREE-MINIMUM(right[x]),如果x其右子树为空,且存在一个后继结点y,则y是x的最低祖先结点,且y的左儿子也是x的祖先(每个结点都是自己的祖先)。
找后继:
TREE-SUCCESSOR(x)
if rithx[x]≠NIL
then return TREE-MINIMUM(right[x])
y←p[x]
while y≠NIL and x=right[y]
do x←y
y←p[y]
return y
同理,对于x找前趋,如果left[x]不为空,那么其前趋结点为TREE-MAXMUM(left[x]),如果其左子树为空,且存在一个前趋结点y,则y是x的最低祖先结点,且y的右孩子也是x的祖先
找前趋:
TREE-PREDECESSOR(x)
if left[x]≠NIL
then return TREE-MAXMUM(left[x])
y←p[x]
while y≠NIL and x=left[x]
do x←y
y←p[y]
return y
插入和删除
二叉查找树的插入和删除都会引起二叉查找树动态集合的变化,在动态集合的变化过程中,要保持二叉查找树的性质。
插入
将一个结点插入到二叉查找树中,总是将这个新结点插入到叶子结点(尽管插入方式不唯一),这样做简单很多。首先找到要插入的位置,然后将结点插入进去。
TREE-INSERT(T,z)
y←NIL
x←root(T)
while x≠NIL
do y←x//y是x的父结点
if key[z]<key[x]
then x←left[x]
else x←right[x]
p[z]←y
if y=NIL//x是空树
then root[T]←z
else if key[y]>key[z]
left[y]←z
else right[y]←z
将一个结点从二叉查找树中删除,相对来说比插入复杂。要分以下三种情况1、此结点是叶子结点,即左右子树为空。这种情况,只需更改其父结点的子指针。2此结点只有一个孩子结点,那么删掉这个结点,把孩子结点顶上去即可。3此结点既有左子树,又有右子树,那么则删除此节点的后继结点,把后继结点替代此结点。
TREE-DELETE(T,z)
if left[z]=NIL or right[z]=NIL//找到要删除的结点y
then y←z//如果左子树或右子树有空,那么要删除的结点就是z
else y←TREE-SUCCESSOR(z)//如果左右子树都不为空,那么要删除的结点就是z的后继结点
if left[y]≠NIL
then x←left[y]
else x←right[y]
if x≠NIL//x不为空,那么x就是y的孩子结点
then p[x]←p[y]
if p[y]=NIL//如果y为根结点
then root[T]←x
else if y=left[p[y]]//y是左子树
then left[p[y]]←x
else right[p[y]]←x
if y≠z//删除掉的是z的后继
then key[z]←key[y]
return y