查找树(搜索树)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了查找树(搜索树)相关的知识,希望对你有一定的参考价值。

参考技术A

为什么引入树?
因为表的线性访问时间太慢。
1)定义
递归的方式定义:
一棵树是一些结点的集合。这个集合可以是空;若非空,则一棵树由称作根的结点r以及0个或多个非空的(子)树T1,T2,...,Tk组成,这些子树中每一棵的根都被来自根r的一条有向的边连接。

父亲、儿子:每一棵子树的根叫做根r的儿子,而r是每棵子树的根的父亲。
树叶:没有儿子的结点称为树叶;
兄弟:具有相同父节点为兄弟(silbing);
路径:从结点n1到nk的路径定义为结点n1, n2, ..., nk的一个序列,使得1 <= i < k,结点ni是ni+1的路径。一棵树中从根到每个结点恰好存在一条路径。
路径长:该路径上边的条数,n1到nk位k-1.
深度:对任意结点ni,ni的深度为从根到ni的唯一路径的长
高:是从ni到一片树叶的最长路径的长
祖先、后裔:如果存在从n1到n2的一条路径,那么n1是n2的一位祖先,而n2是n1的后裔;如果n1不等于n2,则各为真祖先、真后裔。

2)实现
树的结点声明:

一棵树的表示和实现:

3)树的遍历及应用
a.先序遍历:对结点的处理工作是在它的诸儿子结点被处理前进行的。

二叉树的每个结点都不能有多于两个儿子。

二叉树结点的声明:

二叉树在编译器领域的应用:

1)中序遍历:递归产生一个带括号的左表达式,然后打印出根处的运算符,再递归地产生一个带括号的右表达式,得到一个中缀表达式
2)后序遍历:递归打印出左子树、右子树,然后打印运算符(后缀表达式)
3)前序遍历:先打印出运算符,然后递归打印出左子树和右子树

将后缀表达式转变成表达式树:
step1.一次一个符号地读入表达式
step2.如果符号是操作数,那么就建立一个单节点树并将一个指向它的指针推入栈中;如果符号是操作符,那么我们就从栈中弹出指向两棵树T1和T2的那两个指针(T1先弹出)并形成一棵新的树。

a b + c d e + * *后缀表达式转换成表达式树的过程:

支持SEARCH(Find)、MINIMUM(FindMin)、MAXIMUM(Find Max)、PREDECESSOR、SUCCESSOR、INSERT和DELETE等集合操作。
查找树既可以作为一个字典(字典的实现,作为一个专题?)又可以作为一个优先队列。

1)结构
一棵二叉查找树是以一棵二叉树来组织的。

2)操作
a.查找

b.最值

c.前驱和后继

证明:考虑一棵二叉搜索树T,其关键字互不相同。如果T中的一个结点x的右子树为空,且x有一个后继y,那么y一定是x的最底层祖先,并且其左孩子也是x的祖先。(每个结点都是它自己的祖先。)

如上图:
4的后继是6
9的后继是13
13的后继是15

step1.从结点x向上搜索,从右边一路向上,这些结点的key都要小于x
step2.直到找到一个结点,从左边向上,这是第一个大于所有左边结点的结点y
这里的关键是:结点是x是结点y的左子树最大者,因此x的后继是y

d.插入

e.删除
删除分为如下三种情况:

另外一种划分:
e-1.如果z没有左孩子,那么用其右孩子来代替z,这个右孩子可以是NIL,也可以不是。

e-2.如果z仅有一个孩子且是其左孩子,用左孩子来代替z

e-3.否则,z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树中并且没有左孩子。现在需要将y移出原来的位置进行拼接,并替换树中的z
如果y是z的右孩子,那么用y替换z,并仅留下y的右孩子。

否则,y位于z的右子树中但并不是z的右孩子。此时先用y的右孩子替换y,然后再用y替换z。

这里涉及到一个证明:
如果一棵二叉搜索树中的一个结点有两个孩子,那么它的后继没有左孩子,它的前驱没有右孩子。
因为:结点有两个孩子,那么它的后继一定在右子树中,沿着左路径一直向下;如果这个后继还有左孩子,那么后继是这个孩子。
前驱同理。

TRANSPLANT用一棵以v为根的子树来替换一棵以u为根的子树:

3)二叉搜索树的平均情形性能——随机构建二叉搜索树
当一棵二叉搜索树同时由插入和删除操作生成时,我们对这棵树的平均高度了解甚少。
当树由插入操作单独生成时,分析就会容易得多。

定义n个关键字的一棵随机构建二叉搜索树为按随机次序插入这些关键字到一棵初始的空树中而生成的,这里的n!个排列中的每个都是等可能地出现。

证明:说明含有n个关键字的随机选择二叉搜索树的概念,这里每一棵n个结点的二叉搜索树是等可能地被选择,不同于本节中给出的随机构建二叉搜索树的概念。

从上图中可以看出,一棵二叉搜索树可能对应多个排列。因此,不同于本节中给出的随机构建二叉搜索树的概念。

证明:一棵有n个不同关键字的随机构建二叉搜索树的期望高度为O(lgn)。

该证明中涉及到的证明:
1)组合数的证明

2)证明:f(x) = 2的x次方是凸函数。

3)关于e的x次幂 >= 1+x

4)关于Jensen不等式(是凸函数定义的推广)

1)结构
AVL(Adelson-Velskii和Landis)树是带有平衡条件的二叉查找树。
这个平衡条件必须容易保持,而且它必须保证树的深度是O(logn)。
a.最简单的方法是:要求左右子树具有相同的高度。(太松)
b.要求每个结点都必须要有相同高度的左子树和右子树。(太严)
一棵AVL树是其每个结点的左子树和右子树的高度最多差1的二叉查找树。(空树的高度定义为-1)

在高度为h的AVL树中,最少节点数S(h)由S(h) = S(h-1) + S(h-2) + 1给出(g(h) = S(h) + 1)。
对于h = 0, S(h) = 1;h = 1, S(h) = 2。函数S(h)与斐波那契数密切相关。
由此可以推出:n >= S(h),可以得到h的高度最高不超过1.44log(n + 2) - 1.328,也即O(lgn)。

2)操作

重点研究插入:
将关键字X的一个新结点插入到一棵AVL树T中,递归地将X插入到T的相应的子树(Tlr)。
如果Tlr的高度不变,那么插入完成。
否则,如果在T中出现高度不平衡,那么我们根据X以及T和Tlr中的关键做适当的单旋转或双旋转,更新这些高度(并做好与树的其余部分的连接),从而完成插入。

插入后,只有那些从插入点到根节点的路径上的结点的平衡可能被改变,因为只有这些结点的子树发生变换。
对第一个破坏了AVL条件的结点(即最深的结点)进行平衡,这一重新平衡保证整个树满足AVL特性。
该结点为α,由于任意结点最多有两个儿子,因此高度不平衡时,α点两棵子树的高度差2.这种不平衡可能出现下面四种情况:

情况1和情况4关于α点镜像对称,2和3关于α点镜像堆成。

2-1)情况1和情况4——单旋转
针对情况1:
插入之前k2满足AVL特性,插入后结点k2不满足AVL平衡特性,因为它的左子树(X已经长出一层)比右子树深2层(看虚线)。因此,有
a.Y不可能与新X在同一水平上,因为那样k2在插入之前就不平衡了
b.Y也不可能和Z在同一层,因为那样k1就是平衡被破坏的第一个结点

为了恢复平衡,把X上移一层,并把Z下移一层。调整后,整个树的新高度与插入前原树的高度相同。

情况4类似:

2-2)情况2和情况3——双旋转
针对情况2:
恰好B或C中有一棵比D深两层,但是不确定是哪一棵,因此画成比D低1.5层。

保证从空树开始任意连续M次对数的操作最多花费O(MlogN)。虽然这种保证不排除任意一次操作花费O(N)时间。一棵伸展树每次操作的摊还代价是O(logN)。

伸展树是基于这样的事实:对于二叉查找树,每次操作最坏情形时间O(N)并不坏,只要它相对不常发生即可。
因此:当一个结点被访问后,它就要经过一系列AVL树的旋转被放到根上。(因为许多应用中,当一个结点被访问,它就很可能不久再次被访问到,如果碰到O(N)的结点,没有被移动,那么很难得到O(lgN)摊还时间)

伸展树不要求保留高度或平衡信息。

1)一个简单的想法——执行单旋转
如下是对树中k1进行一次访问后发生的情况:

2)展开
X是访问路径上的一个非根结点。
a. X的父节点是树根,只要旋转X和树根
b. X有父亲P和祖父G
b-1. zig-zag情形

b-2. zig-zig情形

考虑之前的例子:

考虑下面这个例子:

(是否可以证明?)展开操作的效果:不仅将访问的结点移到根处,而且还把访问路径上的大部分结点的深度大致减少一半的效果(某些浅的结点最多向下推后两个层次)

3)删除
step1.访问该节点(移到根处)
step2.删除根节点(得到Tl和Tr)
step3.将Tl中的最大元素作为根,并且将Tr接到根的右边,作为右儿子

基本的伸展树展开操作:
直接实现需要从根沿树往下的一次遍历,以及而后的从底向上的一次遍历。
通过保存一些父指针来完成,或者通过将访问路径存储到一个栈中来完成。开销较大。

自顶向下伸展树的改进:
自顶向下展开在初始访问路径上施行一些旋转,结果得到在实践中更快的过程,只用O(1)的额外空间,但却保持了O(logN)的摊还时间界。

step1.自顶向下展开旋转

这个伸展过程不能处理在伸展树中不存在的元素,正确的做法如下:

自顶向下的展开过程为什么是正确的?

证明:自顶向下的展开过程的摊还界为O(lgN)

step2.最后一步:处理L、R和中间树以形成一棵树

示例如下

插入

该伸展树插入19:

删除

以上是关于查找树(搜索树)的主要内容,如果未能解决你的问题,请参考以下文章

PHP 二叉查找树(二叉搜索树)的查找

C++-二叉搜索树的查找&插入&删除-二叉搜索树代码实现-二叉搜索树性能分析及解决方案

二叉搜索树

二叉搜索树和最优二叉搜索树的时间复杂度各是多少?

数据结构查找---二叉搜索树(排序树)

二叉搜索树的查找