前言:
在完成了自底向上的伸展树之后,我决定把自顶向下的伸展树也做出来。不过这个方式《数据结构与算法分析》书上没讲,完全只能通过自学了。实现的方式比较不容易懂,我在阅读了第二篇博客许多遍之后才明白整个过程。
参考的博客:
自底向上:
http://www.cnblogs.com/vamei/archive/2013/03/24/2976545.html。我从这一篇博客中学习了不少的新的思想,比如我最开始总想着利用一个函数递归完成插入或者删除操作,却发现其中有一部分的操作不是每个节点都需要的,这个时候可以把删除操作再分出一个子函数来,需要递归的部分由这个子函数完成就行了。这样使得编码简单很多并且易于阅读。
自顶向下:
http://www.cnblogs.com/kernel_hcy/archive/2010/03/17/1688360.html 。这一篇博客写的非常的详细,并且给出了贴图详细讲诉旋转的过程,来源是sedgewick大神的《算法》一书。我根据给出的流程图和伪代码,完全理解之后,用一个小时完成了编码,并且一次测试通过。
我的github:
我实现的代码全部贴在我的github中,欢迎大家去参观。
https://github.com/YinWenAtBIT
介绍:
定义(与上一篇相同):
伸展树,或者叫自适应查找树,是一种用于保存有序集合的简单高效的数据结构。伸展树实质上是一个二叉查找树。允许查找,插入,删除,删除最小,删除最大,分割,合并等许多操作,这些操作的时间复杂度为O(logN)。由于伸展树可以适应需求序列,因此他们的性能在实际应用中更优秀。
伸展树支持所有的二叉树操作。伸展树不保证最坏情况下的时间复杂度为O(logN)。伸展树的时间复杂度边界是均摊的。尽管一个单独的操作可能很耗时,但对于一个任意的操作序列,时间复杂度可以保证为O(logN)。
AVLTree的缺点:
1、平衡查找树每个节点都需要保存额外的信息(自底向上的实现方式同样保存了额外信息)。
2、难于实现,因此插入和删除操作复杂度高,且是潜在的错误点。
3、对于简单的输入,性能并没有什么提高。
平衡查找树可以提高性能的地方:
1、平衡查找树在最差、平均和最坏情况下的时间复杂度在本质上是相同的。
2、对一个节点的访问,如果第二次访问的时间小于第一次访问,将是非常好的事情。
3、90-10法则。在实际情况中,90%的访问发生在10%的数据上。
4、处理好那90%的情况就很好了。
自顶向下旋转:
在自底向上的伸展树中,我们需要求一个节点的父节点和祖父节点,因此这种伸展树难以实现或者需要额外的信息。因此,我们可以构建自顶向下的伸展树。
当我们沿着树向下搜索某个节点X的时候,我们将搜索路径上的节点及其子树移走。我们构建两棵临时的树──左树和右树。没有被移走的节点构成的树称作中树。在伸展操作的过程中:
1、当前节点X是中树的根。
2、左树L保存小于X的节点。
3、右树R保存大于X的节点。
开始时候,X是树T的根,左右树L和R都是空的。和前面的自下而上相同,自上而下也分三种情况:
1、zig情况。
如上图,在搜索到X的时候,要查的节点比X小,并且Y等于要查找的节点(这种方式其实可以合并在zigzag情况之中)。因此Y是下一步要查找的节点,因此Y变成新的中树的树根,X及其右子树被移动到右树上。很显然,右树上的节点都大于所要查找的节点。注意X被放置在右树的最小的位置,即右树最小节点的左孩子指针上。因为X及其子树比原先的右树中所有的节点都要小。这是由于越是在路径前面被移动到右树的节点,其值越大。读者可以分析一下树的结构,原因很简单。
2、zig-zig情况
如图所示:
在这种情况下,所查找的节点在Z的子树中,也就是,所查找的节点比X和Y都小。所以要将X,Y及其右子树都移动到右树中。首先是Y绕X右旋,然后将Z变成新的中树根节点。将Y及其子树移到右树中。注意右树中挂载点的位置。
3、zig-zig情况。
如图所示:
在这种情况中,先将X及其左子树连接到右树上,然后变成了zag的情况。接下来就可以很轻松的解决了。
合并: 最后,在查找到节点后,将三棵树合并。如图:]
将中树的左右子树分别连接到左树的右子树和右树的左子树上。将左右树作为X的左右子树。重新最成了一所查找的节点为根的树。