PHP算法 《树形结构》 之 伸展树(1) - 基本概念

伸展树的介绍

1、出处:http://www.cnblogs.com/skywang12345/p/3604238.html

伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。
(01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。
(02) 除了拥有二叉查找树的性质之外,伸展树还具有的一个特点是:当某个节点被访问时,伸展树会通过旋转使该节点成为树根。这样做的好处是,下次要访问该节点时,能够迅速的访问到该节点。

假设想要对一个二叉查找树执行一系列的查找操作。为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生,它是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。

相比于"二叉查找树"和"AVL树",学习伸展树时需要重点关注是"伸展树的旋转算法"。

2、出处:http://www.cnblogs.com/vamei/archive/2013/03/24/2976545.html

树的搜索效率与树的深度有关。二叉搜索树的深度可能为n,这种情况下,每次搜索的复杂度为n的量级。AVL树通过动态平衡树的深度,单次搜索的复杂度为log(n) 。我们下面看伸展树(splay tree),它对于m次连续搜索操作有很好的效率。

伸展树会在一次搜索后,对树进行一些特殊的操作。这些操作的理念与AVL树有些类似,即通过旋转,来改变树节点的分布,并减小树的深度。但伸展树并没有AVL的平衡要求,任意节点的左右子树可以相差任意深度。与二叉搜索树类似,伸展树的单次搜索也可能需要n次操作。但伸展树可以保证,m次的连续搜索操作的复杂度为mlog(n)的量级,而不是mn量级。

具体来说,在查询到目标节点后,伸展树会不断进行下面三种操作中的一个,直到目标节点成为根节点 (注意,祖父节点是指父节点的父节点)

1. zig: 当目标节点是根节点的左子节点或右子节点时,进行一次单旋转,将目标节点调整到根节点的位置。

zig

2. zig-zag: 当目标节点、父节点和祖父节点成"zig-zag"构型时,进行一次双旋转,将目标节点调整到祖父节点的位置。

zig-zag

3. zig-zig:当目标节点、父节点和祖父节点成"zig-zig"构型时,进行一次zig-zig操作,将目标节点调整到祖父节点的位置。

zig-zig

单旋转操作和双旋转操作见AVL树。下面是zig-zig操作的示意图:

zig-zig operation

在伸展树中,zig-zig操作(基本上)取代了AVL树中的单旋转。通常来说,如果上面的树是失衡的,那么A、B子树很可能深度比较大。相对于单旋转(想一下单旋转的效果),zig-zig可以将A、B子树放在比较高的位置,从而减小树总的深度。

下面我们用一个具体的例子示范。我们将从树中搜索节点2:

Original

zig-zag (double rotation)

zig-zig

zig (single rotation at root)

上面的第一次查询需要n次操作。然而经过一次查询后,2节点成为了根节点,树的深度大减小。整体上看,树的大部分节点深度都减小。此后对各个节点的查询将更有效率。

伸展树的另一个好处是将最近搜索的节点放在最容易搜索的根节点的位置。在许多应用环境中,比如网络应用中,某些固定内容会被大量重复访问(比如江南style的MV)。伸展树可以让这种重复搜索以很高的效率完成。

3、出处:http://www.cnblogs.com/kernel_hcy/archive/2010/03/17/1688360.html

一、简介:
伸展树,或者叫自适应查找树,是一种用于保存有序集合的简单高效的数据结构。伸展树实质上是一个二叉查找树。允许查找,插入,删除,删除最小,删除最大,分割,合并等许多操作,这些操作的时间复杂度为O(logN)。由于伸展树可以适应需求序列,因此他们的性能在实际应用中更优秀。
伸展树支持所有的二叉树操作。伸展树不保证最坏情况下的时间复杂度为O(logN)。伸展树的时间复杂度边界是均摊的。尽管一个单独的操作可能很耗时,但对于一个任意的操作序列,时间复杂度可以保证为O(logN)。
二、自调整和均摊分析:
    平衡查找树的一些限制:
1、平衡查找树每个节点都需要保存额外的信息。
2、难于实现,因此插入和删除操作复杂度高,且是潜在的错误点。
3、对于简单的输入,性能并没有什么提高。
    平衡查找树可以考虑提高性能的地方:
1、平衡查找树在最差、平均和最坏情况下的时间复杂度在本质上是相同的。
2、对一个节点的访问,如果第二次访问的时间小于第一次访问,将是非常好的事情。
3、90-10法则。在实际情况中,90%的访问发生在10%的数据上。
4、处理好那90%的情况就很好了。
三、均摊时间边界:
在一颗二叉树中访问一个节点的时间复杂度是这个节点的深度。因此,我们可以重构树的结构,使得被经常访问的节点朝树根的方向移动。尽管这会引入额外的操作,但是经常被访问的节点被移动到了靠近根的位置,因此,对于这部分节点,我们可以很快的访问。根据上面的90-10法则,这样做可以提高性能。
为了达到上面的目的,我们需要使用一种策略──旋转到根(rotate-to-root)。具体实现如下:
旋转分为左旋和右旋,这两个是对称的。图示:
 
为了叙述的方便,上图的右旋叫做X绕Y右旋,左旋叫做Y绕X左旋。
下图展示了将节点3旋转到根:
 
                            图1
首先节点3绕2左旋,然后3绕节点4右旋。
注意:所查找的数据必须符合上面的90-10法则,否则性能上不升反降!!
四、基本的自底向上伸展树:
    应用伸展(splaying)技术,可以得到对数均摊边界的时间复杂度。
    在旋转的时候,可以分为三种情况:
1、zig情况。
    X是查找路径上我们需要旋转的一个非根节点。
    如果X的父节点是根,那么我们用下图所示的方法旋转X到根:
     
                                图2
    这和一个普通的单旋转相同。
2、zig-zag情况。
在这种情况中,X有一个父节点P和祖父节点G(P的父节点)。X是右子节点,P是左子节点,或者反过来。这个就是双旋转。
先是X绕P左旋转,再接着X绕G右旋转。
如图所示:
 
                            图三
3、zig-zig情况。
    这和前一个旋转不同。在这种情况中,X和P都是左子节点或右子节点。
    先是P绕G右旋转,接着X绕P右旋转。
    如图所示:
     
                                    图四
    下面是splay的伪代码:
    P(X) : 获得X的父节点,G(X) : 获得X的祖父节点(=P(P(X)))。
    Function Buttom-up-splay:
        Do
            If X 是 P(X) 的左子结点 Then
                If G(X) 为空 Then
                    X 绕 P(X)右旋
                Else If P(X)是G(X)的左子结点
                    P(X) 绕G(X)右旋
                    X 绕P(X)右旋
                Else
                    X绕P(X)右旋
                    X绕P(X)左旋 (P(X)和上面一句的不同,是原来的G(X))
                Endif
            Else If X 是 P(X) 的右子结点 Then
                If G(X) 为空 Then
                    X 绕 P(X)左旋
                Else If P(X)是G(X)的右子结点
                    P(X) 绕G(X)左旋
                    X 绕P(X)左旋
                Else
                    X绕P(X)左旋
                    X绕P(X)右旋 (P(X)和上面一句的不同,是原来的G(X))
                Endif 
            Endif
        While (P(X) != NULL)
    EndFunction
    仔细分析zig-zag,可以发现,其实zig-zag就是两次zig。因此上面的代码可以简化:
    Function Buttom-up-splay:
        Do
            If X 是 P(X) 的左子结点 Then
                If P(X)是G(X)的左子结点
                    P(X) 绕G(X)右旋
                Endif
                X 绕P(X)右旋
            Else If X 是 P(X) 的右子结点 Then
                If P(X)是G(X)的右子结点
                    P(X) 绕G(X)左旋
                Endif 
                X 绕P(X)左旋
            Endif
        While (P(X) != NULL)
    EndFunction
    下面是一个例子,旋转节点c到根上。 
 
                                    图五
五、基本伸展树操作:
1、插入:
    当一个节点插入时,伸展操作将执行。因此,新插入的节点在根上。
2、查找:
    如果查找成功(找到),那么由于伸展操作,被查找的节点成为树的新根。
如果查找失败(没有),那么在查找遇到NULL之前的那个节点成为新的根。也就是,如果查找的节点在树中,那么,此时根上的节点就是距离这个节点最近的节点。
3、查找最大最小:
        查找之后执行伸展。
4、删除最大最小:
a)删除最小:
    首先执行查找最小的操作。
这时,要删除的节点就在根上。根据二叉查找树的特点,根没有左子节点。
使用根的右子结点作为新的根,删除旧的包含最小值的根。
b)删除最大:
首先执行查找最大的操作。
删除根,并把被删除的根的左子结点作为新的根。
5、删除:
        将要删除的节点移至根。
        删除根,剩下两个子树L(左子树)和R(右子树)。
        使用DeleteMax查找L的最大节点,此时,L的根没有右子树。
        使R成为L的根的右子树。
        如下图示:
         
                                图六
六、自顶向下的伸展树:
    在自底向上的伸展树中,我们需要求一个节点的父节点和祖父节点,因此这种伸展树难以实现。因此,我们可以构建自顶向下的伸展树。
    当我们沿着树向下搜索某个节点X的时候,我们将搜索路径上的节点及其子树移走。我们构建两棵临时的树──左树和右树。没有被移走的节点构成的树称作中树。在伸展操作的过程中:
1、当前节点X是中树的根。
2、左树L保存小于X的节点。
3、右树R保存大于X的节点。
开始时候,X是树T的根,左右树L和R都是空的。和前面的自下而上相同,自上而下也分三种情况:
1、zig:
 
                                图七
    如上图,在搜索到X的时候,所查找的节点比X小,将Y旋转到中树的树根。旋转之后,X及其右子树被移动到右树上。很显然,右树上的节点都大于所要查找的节点。注意X被放置在右树的最小的位置,也就是X及其子树比原先的右树中所有的节点都要小。这是由于越是在路径前面被移动到右树的节点,其值越大。读者可以分析一下树的结构,原因很简单。
2、zig-zig:
 
                                图八
    在这种情况下,所查找的节点在Z的子树中,也就是,所查找的节点比X和Y都小。所以要将X,Y及其右子树都移动到右树中。首先是Y绕X右旋,然后Z绕Y右旋,最后将Z的右子树(此时Z的右子节点为Y)移动到右树中。注意右树中挂载点的位置。
3、zig-zag:
 
                            图九
    在这种情况中,首先将Y右旋到根。这和Zig的情况是一样的。然后变成上图右边所示的形状。接着,对Z进行左旋,将Y及其左子树移动到左树上。这样,这种情况就被分成了两个Zig情况。这样,在编程的时候就会简化,但是操作的数目增加(相当于两次Zig情况)。
    最后,在查找到节点后,将三棵树合并。如图:
 
                                图十
    将中树的左右子树分别连接到左树的右子树和右树的左子树上。将左右树作为X的左右子树。重新最成了一所查找的节点为根的树。
    下面给出伪代码:
    右连接:将当前根及其右子树连接到右树上。左子结点作为新根。
    左连接:将当前根及其左子树连接到左树上。右子结点作为新根。
    T : 当前的根节点。
Function Top-Down-Splay 
     Do 
          If X 小于 T Then 
               If X 等于 T 的左子结点 Then  
                 右连接 
               ElseIf X 小于 T 的左子结点 Then 
                 T的左子节点绕T右旋 
                 右连接 
               Else X大于 T 的左子结点 Then 
                 右连接 
                 左连接 
               EndIf    
          ElseIf X大于 T Then 
               IF X 等于 T 的右子结点 Then 
                 左连接 
               ElseIf X 大于 T 的右子结点 Then 
                 T的右子节点绕T左旋 
                 左连接 
               Else X小于 T 的右子结点‘ Then 
                 左连接 
                 右连接 
               EndIf 
          EndIf 
     While  !(找到 X或遇到空节点) 
      组合左中右树 
EndFunction

同样,上面的三种情况也可以简化:
    Function Top-Down-Splay
        Do 
              If X 小于 T Then 
                   If X 小于 T 的左孩子 Then 
                     T的左子节点绕T右旋 
                   EndIf    
                右连接 
              Else If X大于 T Then 
                   If X 大于 T 的右孩子 Then 
                     T的右子节点绕T左旋
                   EndIf 
左连接 
         EndIf 
While  !(找到 X或遇到空节点) 
组合左中右树 
    EndFuntion

下面是一个查找节点19的例子:
    在例子中,树中并没有节点19,最后,距离节点最近的节点18被旋转到了根作为新的根。节点20也是距离节点19最近的节点,但是节点20没有成为新根,这和节点20在原来树中的位置有关系。
 
    这个例子是查找节点c:
 

时间: 2024-10-10 00:31:05

PHP算法 《树形结构》 之 伸展树(1) - 基本概念的相关文章

[转载]伸展树(一)之 图文解析 和 C语言的实现

概要 本章介绍伸展树.它和"二叉查找树"和"AVL树"一样,都是特殊的二叉树.在了解了"二叉查找树"和"AVL树"之后,学习伸展树是一件相当容易的事情.和以往一样,本文会先对伸展树的理论知识进行简单介绍,然后给出C语言的实现.后序再分别给出C++和Java版本的实现:这3种实现方式的原理都一样,选择其中之一进行了解即可.若文章有错误或不足的地方,希望您能不吝指出! 目录 1. 伸展树的介绍 2. 伸展树的C实现 3. 伸展树的

在现在这个社会,你认为软件开发过程中团队模式使用树形结构好还是扁平化结构好?

树形结构:像树一样,有树干(父节点),树叶(子节点)等,将组织机构进行分层,上层管理下层,下层管理下下层.这样的模式优点在于将组织层层细化,条理清晰:缺点在于管理机构太多,另外下级可能不敢违背上级的命令,从而缺乏建设性的意见,是团队项目开发的主要内容掌握在少数人的手中.     扁平化结构:减少管理层级,每个管理层次的管理幅度较大,从而形成管理层次较少的层次结构.这种结构的优点在于信息从下层流通到上层的距离变短,高层了解基层的机会变多,减少了管理者的费用,下级可以有较多的发挥优势的机会,以及提高

伸展树的实现——c++

 一.介绍 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造.(01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key[x]:如果y是x的右子树的一个结点,则key[y] >= key[x].(02) 除了拥有二叉查找树的性质之外

Atitit 常见的树形结构 红黑树 &#160;二叉树 &#160;&#160;B树 B+树 &#160;Trie树&#160;attilax理解与总结

Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树2 1.4. 满二叉树和完全二叉树..完全二叉树说明深度达到完全了.2 1.5. 属的逻辑表示 树形比奥死,括号表示,文氏图,凹镜法表示3 1.6. 二叉树是数据结构中一种重要的数据结构,也是树表家族最为基础的结构.3 1.6.1. 3.2 平衡二叉

Java创建树形结构算法实例

在JavaWeb的相关开发中经常会涉及到多级菜单的展示,为了方便菜单的管理需要使用数据库进行支持,本例采用相关算法讲数据库中的条形记录进行相关组装和排序讲菜单组装成树形结构. 首先是需要的JavaBean 1 2 3 import java.io.Serializable; 4 import java.util.ArrayList; 5 import java.util.Collections; 6 import java.util.Comparator; 7 import java.util.

6天通吃树结构—— 第四天 伸展树

原文:6天通吃树结构-- 第四天 伸展树 我们知道AVL树为了保持严格的平衡,所以在数据插入上会呈现过多的旋转,影响了插入和删除的性能,此时AVL的一个变种 伸展树(Splay)就应运而生了,我们知道万事万物都遵循一个“八二原则“,也就是说80%的人只会用到20%的数据,比如说我们 的“QQ输入法”,平常打的字也就那么多,或许还没有20%呢. 一:伸展树 1:思想 伸展树的原理就是这样的一个”八二原则”,比如我要查询树中的“节点7”,如果我们是AVL的思路,每次都查询“节点7”,那么当这 棵树中

How to print a tree-ADT ? 打印树形结构的算法

How to print a tree-ADT 写和树相关的代码的时候老是不方便debug,因为树形结构虽然能够代码构造出来 但是如果能够有个很好的方法可视化就更好了. 前些天看到一个MIT的代码片段,感激-.... 一开始你可能会想到一种比较简单的迭代实现,就像之前我做的 void putout(int S, int *n) 实现在这里 http://blog.csdn.net/cinmyheart/article/details/43086233 这个函数会打印一个三角形 而我看到MIT老师

【算法】基于树形结构分词

1 #!/usr/bin/env python 2 #encoding=gbk 3 import os 4 import sys 5 6 G_ENCODING="gbk" 7 """ 8 =============================== 9 中文分词 10 1. 机械分词 11 2. 统计分词 12 3. 理解分词 13 =============================== 14 基于树形结构分词策略(结合机械分词,统计分词) 15

算法学习:伸展树(splay)

[定义] [平衡树] 每个叶子结点的深度差不超过1的二叉树 [伸展树] [常用问题] splay的操作,通过左旋右旋,将某个结点通过旋转旋转至根节点,使树的结构发生变化,尽可能的平衡 并且因为左旋右旋的性质,当原树是一个二叉排序树的时候,splay依旧能够使原树保持二叉排序树的性质 左旋右旋图片 [模板题] [luogu P3369]普通平衡树 [题意]实现一颗二叉排序树的增删查改 [注]对数据结构的理解见注释 [代码] #include<cstdio> #include<iostrea