算法导论第十二章:二叉查找树

查找树是一种数据结构,它支持多种动态集合操作,包括search, minimum, maximum, predecessor, successor, insert以及delete。他既可以用作字典,也可以用作优先队列。

二叉查找树上基本操作的执行时间和树的高度成正比。对一棵n个结点的完全二叉树来说,这些操作的最坏情况运行时间为Θ(lgn)。但是如果树是含有那个结点的线性链,则这些操作的最坏运行时间是Θ(n)。本章可以看到一棵随机构造的二叉查找树的期望高度为O(lgn)

实际中,并不能总是保证二叉查找树是随机构造的,但有些二叉查找树的变形能保证各种基本操作的最坏情况性能。第十三章介绍的红黑树,其高度为O(lgn)。第十八章介绍B树,这种结构对维护随机访问的二级存储器上的数据库特别有效。

 

二叉查找树

  二叉查找树是按二叉树结构来组织的,每个结点除了key域和卫星数据外,还包含leftrightp。结点的存储方案满足以下性质:如果结点y是结点x左子树种的一个结点,则key[y]<=key[x]。如果yx右子树中的一个结点,则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_WITHOUTSTACKT

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存在,则yx的后继,否则x没有后继。

 

查找前驱和后继的最坏运行时间为lgn

但对x查找其后继(前驱)的k个结点的运行时间为lgn+k,以前驱为例,证明如下:

假设x的左子树有k1个结点,如果k,则k1时间内就可以完成,假设k1,遍历这k1个结点需要k1时间,找到下一个前驱需要往根方向移动,不妨设移动的距离为h1。如此往复。假设期间产生了 k1,k2,k3..h1,h2…这些数据。可知 k1+k2+k3…<=kh1+h2+…<=树的高度。所以运行时间k1+k2+k3+…+h1+h2+…<=lgn+k

实际上,如果对树的最小结点调用n-1次后继操作,等同于中序遍历二叉查找树。

 

插入删除结点

插入一个结点与查找一个关键字的过程是基本一致的。当查找过程到达nil时,这个位置就是插入的位置。

删除的过程要稍微复杂一点:

(1)       如果结点没有子节点,则直接删除。

(2)       如果结点只有左子树或右子树,则删除该节点,然后用该子树的根节点来取代该节点的位置。

(3)       如果该节点有左右子树,则将该节点的数据与其前驱或后继结点的数据进行交换,再删除该前驱或后继结点

 

随机构造的二叉查找树

二叉查找树的操作性能依赖于树的高度,为了避免坏的关键字顺序造成树的高度过高,可以采用随机化的手段来构造树。可以证明,随机构造的二叉查找树的期望高度为O(lgn)

先定义三个随机变量:

Xnn个结点的二叉查找树的高度;

Yn:指数高度Yn=2XnYn = 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 1001011.白色结点为有效位串的终结结点。

S为一组不同的二进制构成的集合,各串的长度之和为n。说明如何利用基数树,在Θ(n)时间内将S按字典顺序排序。

 

分析:构造基数树的过程很显然,假设一个位串的长度为k,则只需要时间k就可以将串插入基数树中。然后进行中序遍历就可以,按序输出。

如果将基数树扩展,是否可以用来存储一般的字符串,并加快查找过程?

 

你可能感兴趣的:(《算法导论》)