zoukankan      html  css  js  c++  java
  • 伸展树(splay tree)

    伸展树同样是一种平衡二叉搜索树,它的优势在于,在足够长的序列中能保证分摊意义上的高效率,同时也无需记录高度或者平衡因子等信息。

    伸展树的高效前提是局部性:刚刚被访问到的数据,可能在短时间内被再次访问;将被访问的下一元素,可能就在不久之前刚刚访问过的元素的附近。因此,伸展树的策略,就是把刚刚访问到的节点,及时“伸展”到树根附近。

    所谓“伸展”操作,其实就是BST中的旋转操作。如果每次经过适当旋转,将访问的节点提升一层,直到上升到树根,称为逐层伸展。可以验证,这种伸展方式可能会是低效的,而且会导致结构复原,因此,使用双层伸展代替。

    顾名思义,双层伸展就是每次将当前节点v上升两层。根据当前节点v与父亲节点p和祖父g的关系,可以分为三类:

    (1)v p g同侧的时候,此时进行zig-zig或者zag-zag即可。

    (2)v p g不同侧的时候,此时进行zig-zag或者zag-zig即可。

    (3)当v的祖父g已经不存在,即v需要上升的层数为奇数时,只需要简单的zig/zag即可。

    伸展树的代码实现:

     1 #include"BST.h"
     2 template<typename T> class Splay :public BST<T>
     3 {
     4 protected:
     5     BinNodePosi(T) splay(BinNodePosi(T) v);//将节点v伸展至根
     6 public:
     7     BinNodePosi(T)& search(const T& e) override;//查找
     8     BinNodePosi(T) insert(const T& e) override;
     9     bool remove(const T& e) override;
    10 };
     1 template<typename T> BinNodePosi(T) Splay<T>::splay(BinNodePosi(T) v)
     2 {
     3     if (!v) return NULL; BinNodePosi(T) p; BinNodePosi(T) g;
     4     while ((p = v->parent) && (g = p->parent))
     5     {
     6         BinNodePosi(T) gg = g->parent;//每轮之后以原曾祖父为父亲
     7         if(IsLChild(*v))
     8             if (IsLChild(*p)) //zig-zig
     9             {
    10                 attachAsLChild(g, p->rc); attachAsLChild(p, v->rc);
    11                 attachAsRChild(p, g); attachAsRChild(v, p);
    12             }
    13             else//zig-zag
    14             {
    15                 attachAsLChild(p, v->rc); attachAsRChild(g, v->lc);
    16                 attachAsLChild(v, g); attachAsRChild(v, p);
    17             }
    18         else if (IsLChild(*p))
    19         {
    20             attachAsRChild(g, p->lc); attachAsLChild(p, v->rc);
    21             attachAsRChild(p, g); attachAsLChild(v, p);
    22         }
    23         else
    24         {
    25             attachAsRChild(p, v->lc); attachAsLChild(g, v->rc);
    26             attachAsRChild(v, g); attachAsLChild(v, p);
    27         }
    28         if (!gg) v->parent = NULL;//不存在曾祖父,v已经是树根
    29         else//否则,根据g是左子树还是右子树,连接曾祖父和v
    30             (g == gg->lc) ? attachAsLChild(gg, v) : attachAsRChild(gg, v);
    31         updateHegiht(g); updateHeight(p); updateHegiht(v);
    32     }
    33     if (p = v->parent)//如果v还存在父亲,做一次单旋
    34     {
    35         if (IsLChild(*v)) { attachAsLChild(p, v->rc); attachAsRChild(v, p); }
    36         else { attachAsRChild(p, v->lc); attachAsLChild(v, p); }
    37         updateHegiht(p); updateHegiht(v);
    38     }
    39     v->parent = NULL; return v;//此时v必然已经是树根了
    40 }

    可以看到,双旋的部分分为4种位置情况,之后再判断是否需要进行一次单旋,最后返回旋转到树根位置的v节点。

    1 template<typename T> BinNodePosi(T)& Splay<T>::search(const T& e)
    2 {
    3     BinNodePosi(T) p = searchIn(_root, e, _hot = NULL);
    4     _root = spaly(p ? p : _hot);//将最后一个被访问者延伸到树根
    5     return _root;//_root指向最后被访问的节点,要么是查找目标,要么是_hot
    6 }

    查找操作,主体就是查找后进行伸展操作。

     1 template<typename T> BinNodePosi(T) Splay<T>::insert(const T&e)
     2 {
     3     if (!_root) { _size++; return _root = new BinNode<T>(e); }
     4     if (e == search(e)->data) return _root;
     5     _size++;
     6     BinNodePosi(T) t = _root;//创建新节点
     7     if (_root->data < e)//新插入节点大于原根,以t和t->rc作为孩子
     8     {
     9         t->parent = _root = new BinNode<T>(e, NULL, t, t->rc);//插入新节点以及设置父子
    10         if (HasRChild(*t)) { t->rc->parent = _root; t->rc = NULL; }//原根的右子树接入新节点
    11     }
    12     else//...小于原根,把原根节点作为右孩子
    13     {
    14         t->parent = _root = new BinNode<T>(e, NULL, t->lc, t);
    15         if (HasLChild(*t)) { t->lc->parent = _root; t->lc = NULL; }
    16     }
    17     return _root;//返回树根,也就是新插入的节点
    18 }

    插入操作,首先进行查找,判断是否已经存在,并且把查找的结果伸展到树根。再根据根节点与插入的节点大小关系,最终把插入的节点设置为树根。

     1 template<typename T> bool Splay<T>::remove(const T& e)//删除节点,将原节点的父亲升至树根
     2 {
     3     if (!_root || (e != search(e)->data)) return false;//失败时直接返回
     4     BinNodePosi(T) w = _root;
     5     if (!HasLChild(*_root))//如果没有左子树
     6     {
     7         _root = _root->rc;
     8         if (_root) _root->parent = NULL;
     9     }
    10     else if (!HasRChild(*_root))//没有右子树
    11     {
    12         _root = _root->lc;
    13         if (_root) _root->parent = NULL;
    14     }
    15     else //左右子树都存在
    16     {
    17         BinNodePosi(T) lTree = _root->lc;//记录左子树
    18         lTree->parent = NULL;//分离左子树
    19         _root->lc = NULL;
    20         _root = _root->rc; _root->parent = NULL;////保留右子树
    21         search(w->data);//必定失败,但是能把后继(右子树中最小的节点)升至根
    22         _root->lc = lTree; lTree->parent = _root;//接入原左子树
    23     }
    24     _size--;
    25     if(_root) updateHeight(_root);
    26     return true;
    27 }

    删除操作,把被删除节点的父亲升为树根。具体地,如果两个子树有一个不存在,只需要改为左右孩子然后释放原树根节点。如果两个孩子均存在,就需要寻找后继,并升到树根位置。

    最后,分析一下伸展树的优缺点。伸展树的优点,在于分摊效率高,单次操作在O(logn)时间内完成,且对于局部利用率高的情况效率高。缺点也很明显,就是树的结构变化非常大,而且最坏情况下,单次操作最好也需要n的时间。

  • 相关阅读:
    C#
    C#
    ssh学习笔记
    (已解决)Could not open '/var/lib/nova/mnt/*/volume-*': Permission denied
    RPCVersionCapError: Requested message version, 4.17 is incompatible. It needs to be equal in major version and less than or equal in minor version as the specified version cap 4.11.
    如何在linux下安装idea
    The system has no LUN copy license
    调整mysql数据库最大连接数
    mysql数据库编码问题
    cinder支持nfs快照
  • 原文地址:https://www.cnblogs.com/lustar/p/7267444.html
Copyright © 2011-2022 走看看