• 伸展树(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的时间。

  • 相关阅读:
    python字符串操作
    python学习【一】基础入门
    markdown 编辑器
    jenkins学习笔记-安装
    算法
    python 修改文件内容
    python基础,python第四课
    python基础,python第三课
    python基础,python第二课
    python基础,python第一课
  • 原文地址:https://www.cnblogs.com/lustar/p/7267444.html
Copyright © 2020-2023  润新知