二叉查找树是一种树数据结构,它与普通的二叉树最大的不同就是二叉查找树满足一个性质:对于树中的任意一个节点,均有其左子树中的所有节点的关键字值都不大于该节点的关键字值,其右子树中的任意一个节点的关键字值都不小于该节点的关键字值。
在二叉查找树上可以进行搜索、取最小值、取最大值、取指定节点的前驱、取指定节点的后继以及插入和删除节点操作,因此二叉查找树和堆(大顶堆和小顶堆)一样,也可以做优先队列,都能够在 O(lgn) 的时间内取得集合的最大值和最小值。
一个二叉查找树的期望高度为O(lgn),因此在二叉查找树上的基本操作都能在期望时间O(lgn)下实现,但是最坏情况下时间为O(n),也就是二叉排序树极度不平衡,变成一条链。对二叉查找树的前序、中序和后序遍历均是在O(n) 的时间复杂度内实现。根据二叉查找树的性质,其中序遍历的结果就是树元素的按非递减序输出,因此研究二叉排序树的中序遍历及遍历中元素的前驱和后继是非常重要的。
二叉查找树:
对一颗二叉查找树的任何节点,该节点的左子树中的任何一个节点的值都小于等于该节点的值,该节点的右子树中的任何一个节点的值都大于等于该节点的值。
中序遍历二叉查找树,输出的序列是按序的。遍历操作的复杂度O(n)。
如:
根据二叉查找树的性质,可以用一个递归算法按排列顺序输出树中的所有关键字。这种算法称为中序遍历算法,因为一子树根的关键字在输出时介于左子树和右子树的关键字之间(类似地,前序遍历中根的关键字在其左右子树中的关键字之前输出,而后序遍历中根的关键字在其左右子树中的关键字之后输出)。
伪代码:
INORDER-TREE-WALK(x)
if x!=NIL
then INORDER-TREE-WALK(left[x])
print key[x]
INORDER-TREE-WALK(right[x])
查询二叉查找树:
对于二叉查找树,最常见的操作是查找树中的某个关键字。除了SEARCH 操作外, 二叉查找树还能支持诸如MINIMUM、MAXIMUM 、SUCCESSOR 和PREDECESSOR 等查询。
查找:
在二叉查找树中查找一个给定的关键字k的过程与二分查找很类似,根据二叉查找树在的关键字存放的特征,很容易得出查找过程:首先是关键字k与树根的关键字进行比较,如果k大比根的关键字大,则在根的右子树中查找,否则在根的左子树中查找,重复此过程,直到找到与遇到空结点为止。
如:
查找最大关键字和最小关键字:
根据二叉查找树的特征,很容易查找出最大和最小关键字。查找二叉树中的最小关键字:从根结点开始,沿着各个节点的left指针查找下去,直到遇到NULL时结束。如果一个结点x无左子树,则以x为根的子树中,最小关键字就是key[x]。查找二叉树中的最大关键字:从根结点开始,沿着各个结点的right指针查找下去,直到遇到NULL时结束。
前驱和后继:
查找前驱步骤:先判断x是否有左子树,如果有则在left[x]中查找关键字最大的结点,即是x的前驱。如果没有左子树,则从x继续向上执行此操作,直到遇到某个结点是其父节点的右孩子结点。
如:
查找后继步骤:先判断x是否有右子树,如果有则在right[x]中查找关键字最小的结点,即使x的后继。如果没有右子树,则从x的父节点开始向上查找,直到遇到某个结点是其父结点的左儿子的结点时为止。
如:
伪代码:
TREE-SEARCH(x,k)
if x=NIL or k=key[x]
then return x
if k<key[x]
then return TREE-SEARCH(left[x],k)
else return TREE-SEARCH(right[x],k)
//或也可以这么写
ITERATIVE-TREE-SEARCH(x,k)
while x!=NIL and k!=key[x]
do if k < key[x]
then x <- left[x]
else x <- right[x]
return x
TREE-MINIMUM(x)
while left[x]!=NIL
do x <- left[x]
return key[x]
TREE-MAXIMUM(x)
while right[x]!=NIL
do x <- right[x]
return x
TREE-SUCCESSOR(x)
if right[x]!=NIL
return TREE-MINIMUM(right[x])
y <- p[x]
while y!=NIL and x=right[y]
do x <- y
y <- p[x]
return y
插入和删除:
插入结点的位置对应着查找过程中查找不成功时候的结点位置,因此需要从根结点开始查找带插入结点位置,找到位置后插入即可。插入过程运行时间为O(h),h为树的高度。
如:
从二叉查找树中删除给定的结点z,分三种情况讨论:
1、结点z没有左右子树,则修改其父节点p[z],使其为NULL。
如:
2、如果结点z只有一个子树(左子树或者右子树),通过在其子结点与父节点建立一条链来删除z。
如:
3、如果z有两个子女,则先删除z的后继y(y没有左孩子),在用y的内容来替代z的内容。
如:
对高度为h的二叉查找树,动态集合操作INSERT和DELETE的运行时间为O(h)。
伪代码:
TREE-INSERT(T,z)
y <- NIL
x <- root[T]
while x!=NIL
do y <- x
if key[z] < key[x]
then x <- left[x]
else x <- right[x]
p[z] <- y
if y=NIL
then root[T] <- z
else if key[z] < key[y]
then left[y] <- z
else right[y] <- z
TREE-DELETE(T,z)
if left[z]=NIL or right[z]=NIL
then y <- z
else y <- TREE-SUCCESSOR(T,z)
if left[y]!=NIL
then x <- left[y]
else x <- right[y]
if x!=NIL
then p[x] <- p[y]
if p[y]=NIL
then root[T] <- x
else if y=left[p[y]]
then left[p[y]] <- x
else right[p[y]] <- x
if y!=z
then key[z] <- key[y]
return y
随机构造的二叉查找树:
我们可以定义在n个不同的关键字上的一棵随机构造的二叉查找树,它是通过按随机的顺序,将各关键字插入一棵初始为空的树而形成的,并且各输入关键字的n!种排列是等可能的。一棵随机构造的二叉查找树的期望高度为O(lgn),从而基本动态集合的操作平均时间为θ(lgn)。 假设所有的关键字都是不同的。
先定义三个随机变量:
Xn:n个结点的二叉查找树的高度;
Yn:指数高度Yn=2的Xn次方;Yn = 2*max(Yi-1,Yn-i)。
Rn:根节点的在关键字中的统计顺序。
Zn,i:定义指示器随机变量Zn,i=I{Rn=i}; E[Zn,i] = 1/n。
推理过程:
利用恒等式:
用数学归纳得出
再利用jensen不等式:
可得E[Xn] = O(lgn)。