伸展树详解(SplayTree)(可视化工具)

参考:邓俊辉 的数据结构,图片来自该资料

除了AVL树,本章将按照二叉搜索树的介绍,继续介绍平衡二叉搜索树家族中的另一个成员—Splay伸展树。

伸展树(SplayTree)

相对于AVL,Splay的实现更为简捷。伸展树无需时刻都严格地保持全树的平衡,但却能够在任何足够长的真实操作序列中,保持分摊意义上的高效率。伸展树也不需要对基本的二叉树节点结构,做任何附加的要求或改动,更不需要记录平衡因子或高度之类的额外信息,故适用范围更广。

通常在任意数据结构的生命期内,执行不同操作的概率往往极不均衡,而且各操作之间具有极强的相关性,并在整体上多呈现出极强的规律性。其中最为典型的,就是所谓的“数据局部性”(data locality),这包括两个方面的含义:

  • 刚刚被访问过的元素,极有可能在不久之后再次被访问到
  • 将被访问的下一元素,极有可能就处于不久之前被访问过的某个元素的附近

如果将该策略应用于二叉搜索树。只需将刚被访问的节点,及时地“转移”至树根(附近),即可加速后续的操作。当然, 转移前后的搜索树必须相互等价,故为此使用前文介绍的“旋转“等价变换的技巧。

逐层伸展

每访问过一个节点之后,随即反复地以它的父节点为轴,经适当的旋转将其提升一层,直至最终成为树根。以下图为例,若深度为3的节点E刚被访问–无论查找或插入,甚至“删除”都可通过3次旋转,将该树等价变换为以E为根的另一棵二叉搜索树
在这里插入图片描述
随着节点E的逐层上升,两侧子树的结构也不断地调整,故这一过程也形象地称作伸展 (splaying),而采用这一调整策略的二叉搜索树也因此得名。不过,为实现真正意义上的伸 展树,还须对以上策略做点微妙而本质的改进。之所以必须改进,是因为目前的策略仍存在致命 的缺陷—对于很多访问序列,单次访问的分摊时间复杂度在极端情况下可能高达n。

不难验证,若从空树开始依次插入关键码{ 1, 2, 3, 4, 5 },且其间采用如上调整策略,
则可得到如下图所示的二叉搜索树。
在这里插入图片描述
在各次访问之后,为将对应节点伸展调整至树根,分别需做4、4、3、2和1次旋转。

一般地,若节点总数为n,则旋转操作的总次数应为:
(n - 1) + { (n - 1) + (n - 2) + … + 1 }= (n2 +n-2)/2 = O( n2)。

如此分摊下来,每次访问平均需要n时间。很遗憾,这一效率不仅远远低于AVL树,而且甚至与原始的二叉搜索树的最坏情况相当。

而事实上,问题还远不止于此。稍做比对即不难发现,上图a与f中二叉搜索树的结构完全相同。也就是说,经过以上连续的5次访问之后,全树的结构将会复原!
这就意味着,以上情况可以持续地再现。
当然,这一实例,完全可以推广至规模任意的二叉搜索树。于是对于规模为任意n的伸展树, 只要按关键码单调的次序,周期性地反复进行查找,则无论总的访问次数m >> n有多大,就分摊意义而言,每次访问都将需要O(n)时间!

双层伸展

为克服上述伸展调整策略的缺陷,一种简便且有效的方法就是:将逐层伸展改为双层伸展。 具体地,每次都从当前节点v向上追溯两层(而不是仅一层),并根据其父亲p以及祖父g的相对位置,进行相应的旋转。主要以下分三类情况:

zig-zig/zag-zag

如下图所示, 设v是p的左孩子,且p也是g的左孩子;
设W和X分别是v的左、右子树,Y和Z分别是p和g的右子树。

在这里插入图片描述
针对这种情况,首先以节点g为轴做顺时针旋转zig(g),其效果如图(b)所示。然后,再以p
为轴做顺时针旋转zig§,其效果如图©所示。如此连续的两次zig旋转,合称zig-zig调整。 自然地,另一完全对称的情形,v是p的右孩子,且p也是g的右孩子,则可通过连续的
两次逆时针旋转实现调整,合称zag-zag操作。

zig-zag/zag-zig

如下图所示,设v是p的左孩子,而p是g的右孩子;
设W是g的左子树,X和Y分别是v的左右子树,Z是p的右子树。

在这里插入图片描述

针对这种情况,首先以节点p为轴做顺时针旋转zig§,其效果如(b)所示。然后,再以g
为轴做逆时针旋转zag(g),其效果如图©所示。如此zig旋转再加zag旋转,合称zig-zag调整。 同样地,另一完全对称的情形–v是p的右孩子,而p是g的左孩子—则可通过zag旋转再加zig旋转实现调整,合称zag-zig操作。

zig/zag

如下图所示,若v最初的深度为奇数,则经过若干次双层调整至最后一次调整时,
v的父亲p即是树根r。

在这里插入图片描述

将v的左、右子树记作X和Y,节点p = r的另一子树记作Z。
此时,只需围绕p = r做顺时针旋转zig§,即 可如图(b)所示,使v最终攀升至树根,从而结束整个伸展调整的过程。

效果与效率

综合以上各种情况,每经过一次双层调整操作,节点v都会上升两层。若v的初始深度depth(v) 为偶数,则最终v将上升至树根。若depth(v)为奇数,则当v上升至深度为1时,不妨最后再相应 地做一次zig或zag单旋操作。无论如何,经过depth(v)次旋转后,v最终总能成为树根。

回顾最开始的单层伸展的例子中:在可持续重复的过程中,二叉搜索树的高度始终不小于n/2; 而且,至少有一半的节点在接受访问时,不仅没有如最初设想的那样靠近树根,而且反过来恰恰处于最底层。 从树高的角度看,问题根源也可再进一步地解释为:在持续访问的过程中,树高依算术级数逐步从n - 1递减至n/2,然后再逐步递增回到n - 1。

以如下图所示的二叉搜索树为例,在find(1)操作之后,采用逐层调整策略与双层调 整策略的效果,分别如图(a)和图©所示。

在这里插入图片描述
可见,最深节点(1)被访问之后再经过双层调整,不仅同样可将该节点伸展至树根,而且同时可使树的高度接近于减半。就树的形态而言,双层伸展策略可“智能”地“折叠”被访问的 子树分支,从而有效地避免对长分支的连续访问。这就意味着,即便节点v的深度为n,双层 伸展策略既可将v推至树根,亦可令对应分支的长度以几何级数(大致折半)的速度收缩。

伸展代码

在这里插入图片描述

        /// <summary>
        /// 伸展操作
        /// Zig:右旋转,顺时针旋转
        /// Zag:左旋转,逆时针旋转
        /// 
        ///  - 单旋(单层伸展)                                                                                                                                                                                                                                                
        ///              zig                              zag        
        ///        (顺时针旋转g)                   (逆时针旋转g)                                                                                                                                                                                                               
        ///               g                                g                                                                                                                                                                                                                                      
        ///              / \                              / \                                                                                                                                                                                                                                     
        ///             p   T3                           T0  p                                                                                                                                                                                                                                    
        ///            / \                                  / \                                                                                                                                                                                                                                   
        ///           v   T2                               T1  v                                                                                                                                                                                                                                  
        ///          / \                                      / \                                                                                                                                                                                                                                 
        ///         T0  T1                                   T2  T3     
        ///
        ///       zig单旋后:                          zag单旋后:                                                                                                                                                                                                      
        ///              p                                  p                                                                                                                                                                                                              
        ///           /     \                            /     \                                                                                                                                                                                                         
        ///          v       g                          g       v                                                                                                                                                                                                        
        ///         / \     / \                        / \     / \                                                                                                                                                                                                       
        ///        T0  T1  T2  T3                     T0  T1  T2  T3       
        /// 
        ///  - 双旋(双层伸展)
        ///                    zig-zig                               zig-zag                               zag-zag                                zag-zig
        ///       (先顺时针旋转g,后顺时针旋转p) (先顺时针旋转p(zig),后逆时针旋转g(zag))  (先逆时针旋转g,后逆时针旋转p)  (先逆时针旋转p(zag),后顺时针旋转g(zig))
        ///                      g                                     g                                      g                                      g                                                                                                                                                                                                                                       
        ///                     / \                                   / \                                    / \                                    / \                                                                                                                                                                                                          
        ///                    p   T3                               T0   p                                  T0  p                                  p   T3                                                                                                                                                                                                       
        ///                   / \                                       / \                                    / \                                / \                                                                                                                                                                             
        ///                  v   T2                                    v   T3                                 T1  v                              T0  v                                                                                                                                                                                                             
        ///                 / \                                       / \                                        / \                                / \                                                                                                                                                                                                          
        ///                T0  T1                                    T1  T2                                     T2  T3                             T1  T2                                                                                                                                                                               
        ///
        ///       双旋后:  zig-zig 双旋后                        zig-zag双旋后                        zag-zag 双旋后                          zag-zig双旋后                                                                                                                                                                                                 
        ///                      v                                      v                                     v                                       v                                                                                                                                                                             
        ///                     / \                                 /     \                                 / \                                   /     \                                                                                                                                                        
        ///                    T0   p                              g       p                               p   T3                                p       g                                                                                                                                                                                      
        ///                        / \                            / \     / \                             / \                                   / \     / \                                                                                                                                                                                                                                                                            
        ///                       T1  g                          T0  T1  T2  T3                          g   T2                                T0  T1  T2  T3                                                                                                                                                                                                                    
        ///                          / \                                                                / \                                                                                                                                                                                                                          
        ///                         T2  T3                                                             T0  T1                                                                                                                                                                                                                                                     
        ///
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public Node Splay(Node v)
        {
            if (v==null) return null;
            Node g=null, p=null;

            while(!!(p=v.Parent)&& !!(g=p.Parent))//自下而上,反复对v做双层伸展
            {
                Node r = g.Parent;
                if(IsLeft(v))
                {
                    if(IsLeft(p)) //zig-zig   先旋转v的祖父节点,然后再旋转v的父节点
                    {
                        AttachAsLChild(g, p.R);AttachAsLChild(p, v.R);
                        AttachAsRChild(p, g);AttachAsRChild(v, p);
                    }
                    else //zig-zag   先旋转v的父节点,后旋转v的祖父节点
                    {
                        AttachAsLChild(p, v.R); AttachAsRChild(g, v.L);
                        AttachAsLChild(v, g); AttachAsRChild(v, p);
                    }
                }
                else
                {
                    if (IsRight(p))// zag-zag
                    {
                        AttachAsRChild(g, p.L); AttachAsRChild(p, v.L);
                        AttachAsLChild(p, g); AttachAsLChild(v,p);
                        
                    }
                    else  //zag-zig
                    {
                        AttachAsRChild(p, v.L); AttachAsLChild(g, v.R);
                        AttachAsRChild(v, g); AttachAsLChild(v, p);
                    }
                }

                if (r == null)//若原v的曾祖父r不存在,则v现在为树根Root
                {
                    v.Parent = null;
                }
                else
                {
                    if (g == r.L)
                        AttachAsLChild(r, v);
                    else
                        AttachAsRChild(r, v);
                }
                UpdateHeight(g);
                UpdateHeight(p);
                UpdateHeight(v);

            }//双层伸展结束时,必有g=null,但p可能非空

            if(p==v.Parent&&p!=null) //如果p为非空,则需要做一次单旋
            {
                if(IsLeft(v)) /* zig */
                {
                    AttachAsLChild(p, v.R);AttachAsRChild(v, p);
                }
                else /* zag */
                {
                    AttachAsRChild(p, v.L); AttachAsLChild(v, p); 
                }
                UpdateHeight(p);UpdateHeight(v);
            }

            v.Parent = null;
            return v;
        }

伸展树全部代码(C#)

全部代码(C#)

伸展树测试

全部代码(C#)

测试代码
        static void Main()
        {
            SplayTree tree = new SplayTree();
            Node n, n1, n2;
            tree.Insert(20);
            tree.Insert(10);
            tree.Insert(7);  
            tree.Insert(24);
            tree.Insert(26); 
            tree.Insert(12); 
            tree.Insert(18); 
            Console.Write("   Preorder::");
            tree.Preorder(tree.Root);
            Console.WriteLine();
            Console.Write("    Inorder::");
            tree.Inorder(tree.Root);
            Console.WriteLine();
            Console.Write("  Postorder::");
            tree.Postorder(tree.Root);
            Console.WriteLine();
            Console.Write(" Levelorder::");
            tree.Levelorder(tree.Root);
            Console.WriteLine();
            Console.Write(" ZLevelorder:");
            tree.ZLevelorder(tree.Root);
            Console.WriteLine("\n\n");

            tree.Remove(24);
            tree.Remove(20);
            tree.Remove(10);
            tree.Remove(18); 
            tree.Remove(7);
            tree.Remove(26);
            tree.Remove(16);
            tree.Remove(12);


            Console.ReadKey();
        }
测试结果
Insert:20
020

Insert:10
   010
   / \
 N    020

Insert:7
         007
         / \
    N          010
   / \         / \
 N     N     N    020

Insert:24
                     024
                     / \
         020                      N
         / \                     / \
   010          N           N           N
   / \         / \         / \         / \
007    N     N     N     N     N     N     N

Insert:26
                                             026                                                                        
                                             / \                                                                        
                     024                                              N
                     / \                                             / \
         020                      N                       N                       N
         / \                     / \                     / \                     / \
   010          N           N           N           N           N           N           N
   / \         / \         / \         / \         / \         / \         / \         / \
007    N     N     N     N     N     N     N     N     N     N     N     N     N     N     N

Insert:12
                     012
                     / \
         010                     026
         / \                     / \
   007          N          020          N
   / \         / \         / \         / \
 N     N     N     N     N    024    N     N

Insert:18
                     018
                     / \
         012                     020
         / \                     / \
   010          N           N          026
   / \         / \         / \         / \
007    N     N     N     N     N    024    N

   Preorder::18,12,10,7,20,26,24,
    Inorder::7,10,12,18,20,24,26,
  Postorder::7,10,12,24,26,20,18,
 Levelorder::18,12,20,10,26,7,24,
 ZLevelorder:18,20,12,10,26,24,7,


Remove:24
                                             026                                                                        
                                             / \                                                                        
                     018                                              N
                     / \                                             / \
         012                     020                      N                       N
         / \                     / \                     / \                     / \
   010          N           N           N           N           N           N           N
   / \         / \         / \         / \         / \         / \         / \         / \
007    N     N     N     N     N     N     N     N     N     N     N     N     N     N     N

Remove:20
                                             026                                                                        
                                             / \                                                                        
                     018                                              N
                     / \                                             / \
         012                      N                       N                       N
         / \                     / \                     / \                     / \
   010          N           N           N           N           N           N           N
   / \         / \         / \         / \         / \         / \         / \         / \
007    N     N     N     N     N     N     N     N     N     N     N     N     N     N     N

Remove:10
         012
         / \
   007         026
   / \         / \
 N     N    018    N

Remove:18
         026
         / \
   012          N
   / \         / \
007    N     N     N

Remove:7
   012
   / \
 N    026

Remove:26
012

Remove:16
012

Remove:12
 N

可视化工具

可视化工具(旧金山大学 (usfca)|数据结构可视化工具)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值