查找树是一种数据结构,它支持多种动态集合操作,包括search, minimum, maximum, predecessor, successor, insert以及delete。他既可以用作字典,也可以用作优先队列。
二叉查找树上基本操作的执行时间和树的高度成正比。对一棵n个结点的完全二叉树来说,这些操作的最坏情况运行时间为Θ(lgn)。但是如果树是含有那个结点的线性链,则这些操作的最坏运行时间是Θ(n)。本章可以看到一棵随机构造的二叉查找树的期望高度为O(lgn)。
实际中,并不能总是保证二叉查找树是随机构造的,但有些二叉查找树的变形能保证各种基本操作的最坏情况性能。第十三章介绍的红黑树,其高度为O(lgn)。第十八章介绍B树,这种结构对维护随机访问的二级存储器上的数据库特别有效。
二叉查找树是按二叉树结构来组织的,每个结点除了key域和卫星数据外,还包含left,right和p。结点的存储方案满足以下性质:如果结点y是结点x左子树种的一个结点,则key[y]<=key[x]。如果y是x右子树中的一个结点,则key[x]<=key[y]。
对二叉查找树进行中序遍历可以有序输出所有的结点关键字。
练习:
12.1.3给出二叉树的一个非递归的中序遍历算法。
(1)可以用一个栈数据结构来模拟递归过程中的栈变化过程,从而完成非递归形式的遍历算法;
(2)也可以不借助栈数据结构,中序遍历的过程中是由从根到叶、从父到子的down过和从子到父的up过程组成的。在非递归的情况下,在处理某个节点x时,关键是要分清下一步是要down还是up。其实这可以由上一个结点位置来判断,如果上一个结点是x的父节点且x是它的左儿子,则应该往左down,如果x是它的右儿子,则应该up。如果上一结点是x的左儿子,则应该往右down,如果是x的右儿子,则应该up。
OPTYPE {down, up}
INORDER_WITHOUTSTACK(T)
1 Node x = root[T]
2 Node last_node = nil
3 OPTYPE last_op = down
4 while (!over)
5 switch (last_op)
6 Case down:
7 If (left[x]) last_node=x, last_op=down, x=left[x]
8 Else output(x);
9 if (right[x]) last_node=x, last_op=down, x=right[x]
10 Else if (x.p) last_node=x,last_op=up,x=x.p
11 Else over=true
12 Case up:
13 If (last_node = left[x]) output(x)
14 If (right[x]) last_node=x, last_op=down, x=right[x]
15 Else if (x.p) last_node=x,last_op=up,x=x.p
16 Else over=true
17 Else if (x.p) last_node=x,last_op=up,x=x.p
18 Else over=true
查找关键字k:
从树根开始比较key[x]和k,如果key[x]=k停止,如果key[x]>k则往左子树查找,否则往右子树查找,如此循环。
最大关键字元素和最小关键字元素:
最大关键字就是树的最右结点:沿树根往右子节点移动,直到NIL。
最小关键字元素就是树的最左结点:沿树根往左子结点移动,直到NIL。
前驱和后继:
某个节点x的前驱:如果x有左子,那么前驱是左子树的最大关键字;否则从下往上查看x的祖先结点,直到某个节点y满足性质:x出现在y的右子树种。如果y存在,则y就是x的前驱,否则x没有前驱。
某个节点x的后继:如果x有右子,则x的后继是右子树的最小关键字;否则从下往上查看x的祖先结点,直到某个结点y满足性质:x出现在y的左字数中,如果y存在,则y是x的后继,否则x没有后继。
查找前驱和后继的最坏运行时间为lgn。
但对x查找其后继(前驱)的k个结点的运行时间为lgn+k,以前驱为例,证明如下:
假设x的左子树有k1个结点,如果k
实际上,如果对树的最小结点调用n-1次后继操作,等同于中序遍历二叉查找树。
插入一个结点与查找一个关键字的过程是基本一致的。当查找过程到达nil时,这个位置就是插入的位置。
删除的过程要稍微复杂一点:
(1) 如果结点没有子节点,则直接删除。
(2) 如果结点只有左子树或右子树,则删除该节点,然后用该子树的根节点来取代该节点的位置。
(3) 如果该节点有左右子树,则将该节点的数据与其前驱或后继结点的数据进行交换,再删除该前驱或后继结点
二叉查找树的操作性能依赖于树的高度,为了避免坏的关键字顺序造成树的高度过高,可以采用随机化的手段来构造树。可以证明,随机构造的二叉查找树的期望高度为O(lgn)。
先定义三个随机变量:
Xn:n个结点的二叉查找树的高度;
Yn:指数高度Yn=2Xn;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)。
在构造二叉树的时候,当选定某个关键字称为根节点之后,那么比这个关键字大的关键字都将出现在该节点的右字数,小的关键字都将出现在左子树。这个过程与快速排序选定中轴节点的过程及其相似。进一步,所有的结点都将与根节点进行比较,但左子树中的结点不会与右子树的结点比较,反之亦然。可以看出构造二叉查找树的过程与快速排序的过程惊人地相似,实际上如果将选定根节点和选定中轴结点相对应,那么两者整个过程中所进行的元素比较是完全相同的,只是顺序不一致而已。
可以分析二叉查找树的平均结点深度。每个结点的深度等于其在插入树的过程中的比较次数,所有的比较次数之和平均为nlgn,因此平均的结点深度为lgn。
基数树:
基数树数据结构,用来存储0,1位串,位置串并没有存储在节点中,一个结点只要标明从根到该节点的路径是否构成一个有效位串。当查找某关键字a=a0a1…ap,时,在深度为i的一个结点处,如果ai=0,则向左转,如果ai=1则向右转。下图的基数树存储了位串0,001,10, 100,1011.白色结点为有效位串的终结结点。
设S为一组不同的二进制构成的集合,各串的长度之和为n。说明如何利用基数树,在Θ(n)时间内将S按字典顺序排序。
分析:构造基数树的过程很显然,假设一个位串的长度为k,则只需要时间k就可以将串插入基数树中。然后进行中序遍历就可以,按序输出。
如果将基数树扩展,是否可以用来存储一般的字符串,并加快查找过程?