搜索树数据结构支持许多动态集合操作,包括:SEARCH, MINIMUM, MAXIMUM, INSERT, DELETE, SUCCESSOR等。因此一个搜索树既可以作为一个字典又可以作为一个优先队列。二叉搜索树的基本操作所花费的时间与这棵树的高度成正比。对于一个n个结点的二叉搜索树,他的期望时间为O(lgn)
二叉搜索树顾名思义就是一颗可以用链表数据结构来表示的二叉树来组织的。
设x是二叉搜索树中的一个结点。如果y是x左子树的一个结点,那么y.key<= x.key。如果y是x右子树中的一个结点,那么y.key >= x.key
根据其性质我们可以用一个简单的递归算法来按序输出二叉搜索树的所有关键字,这种算法称为中序遍历算法。因为输出的子树根的关键字在左子树的关键字和右子树的关键字之间。此外还有先序遍历和后序遍历
下面给出中序遍历的伪代码:
INORDER-TREE-WALK(x)
if x!= NIL
INORDER-TREE-WALK(x.left)
print x.key
INORDER-TREE-WALK(x.right)
定理12.1 如果x是一棵有n个结点子树的根, 那么调用INORDER-TREE-WALK(x)需要Θ(n)
我们经常需要查找存储在二叉搜索树内的关键字。这节将讨论这些操作,并且说明在任何高度为h的二叉搜索树上,如何在O(h)时间内执行完每个操作。
查找二叉搜索树中一个给定关键字的结点。输入一个指向树根的指针和一个关键字k,如果这个结点存在,TREE-SEARCH返回一个关键字为k的结点的指针,否则返回NIL。
TREE-SEARCH(x, k)
if x == NIL or k == x.key
return x
if k < x.key
return TREE-SEARCH(x.left, k)
else
return TREE-SEARCH(x.right, k)
我们可以看出,这个算法的运行时间为O(h),其中h就是这棵树的高度。
书中还说了一种迭代法,就是用while循环来展开递归,这种效率要高很多:
ITERACTIVE-TREE-SEARCH(x, k)
while x != NIL and k != x.key
if k < x.key
x = x.left
else x = x.right
return x
通过从树根开始沿着left孩子指针知道遇到一个NIL,我们总能在一颗二叉搜索树中找到一个元素。因此下列伪代码指出可以查找到最小元素的指针,这里假设不是NIL:
TREE-MINIMUM(x)
while x.left != NIL
x = x.left
return x
相似的,最大元素查找方法是一样的:
TREE-MIXIMUM(x)
while x.right != NIL
x = x.right
return x
给定一棵二叉搜索树中的一个结点,有时候需要按中序遍历的次序查找它的后继。如果所有的关键字互不相同,则一个结点x的后继是大于工x. key的最小关键字的结点。一棵二叉搜索树的结构允许我们通过没有任何关键字的比较来确定一个结点的后继。如果后继存在,下面的过程将返回一棵二叉搜索树中的结点x的后继;如果x是这棵树中的最大关键字,则返回NIL.
TREE-SUCCESSOR(X)
if x.right != NIL
return TREE-MINIMUN(x.right)
y = x.p
while y != NIL and x == y.right
x = y
y = y.p
return y
定理12.2.
在一棵高度为h的二叉搜索树上,动态集合上的操作SEARCH、MINIMUM、MAXIMUM、SUCCESSOR和PREDECESSOR可以在O(h)
时间内完成。
插入和删除操作会引起二叉搜索树表示的动态集合的变化。一定要修改数据结构来反映这个变化,但修改要保证二叉搜索树性质的成立。正如下面将要看到的,插入一个新的结点带来的树要修改的相对简单一点,而删除的处理有些复杂。
要将一个新值v插入到一颗二叉搜索树T中,需要调用过程TREE-INSERT。该过程以结点z作为输入,其中z.key = v, z.left = NIL, z.right = NIL。这个过程要修改T和z的某些属性,来吧z插入到树中的相应位置上。
TREE-INSERT(T, z)
y = NIL
x = T.root
while x != NIL
y = x
if z.key < x.key
x = x.left
else x = x.right
z.p = y
if y == NIL
T.root = z
else if z.key < y.key
y.left = z
else y.right = z
下图诠释了插入操作的过程:
即向一个二叉搜索树中插入一个x结点,只需要不断比较x->key与当前结点z->key的大小,若小于,则肯定向z的左子树插入,否则向z的右子树插入,循环比较,直到遇到当前结点的左/右子树为空为止。
删除z结点操作要比较麻烦一些,因为要保持二叉搜索树的性质:
因为数学水平有限,对随机构建二叉搜索树没有做记录。还有删除操作还不是特别清楚,不过先记录下来,写代码实践的时候再慢慢的消化吧。然后就是二叉搜索树的时间,平均下来可以类似于折半查找,Θ(lgn)的时间,但是当搜索树完全不平衡时就可能达到O(n)的时间,因此二叉搜索树的时间复杂度为O(lgn)~O(n)