伸展树

基本概念介绍

  • 伸展树也是平衡二叉搜索树的一种性事件。相对于AVL它不需要保持树的平衡,但是可以保持高效率。另外不需要记录平衡因子,高度,适用范围更广
  • 数据的局部性, 伸展树将访问过的节点伸展至根可以有效利用实际数据访问概率不均衡的特点,提升效率
    1. 刚刚被访问到的节点,极有可能在不久之后再次被访问到
    2. 将被访问的下一节点, 极有可能就处在刚刚被访问过的某个节点附近
  • 简易伸展树
    • 实现方式
      • 以访问节点的父级节点为轴反复的旋转,直至根节点
    • 致命缺点
      • 最坏的情况 只要按照关键码单调的次序,周期性的反复查找,复杂度O(n)
  • 双层伸展树
    • 每次都从当前访问节点向上追溯两层,并根据父亲p和祖父g的相对位置反复的旋转,直到根节点。若v的深度为奇数,则经过若干次双层旋转之后,还需要进行一次单旋
  • 双层伸展策略
    • zip – zip
      在这里插入图片描述
    • zap – zap
      在这里插入图片描述
    • zip – zap
      在这里插入图片描述
    • zap – zip
      在这里插入图片描述
    • (单旋)
      在这里插入图片描述
  • 伸展树查询算法
    • 借用二叉搜索树的searchIn算法,找到对应的节点或者父级节点,并将这个节点伸展至根节点
  • 伸展树插入算法
    1. 借助伸展树的查询算法将对应的元素的父级伸展至根节点
    2. 比较根节点关键码和待插入的节点关键码e, 以_root.data < e为例
      a. 从原树T剥离右子树RTree
      b. 以e为关键码的新节点v为树根, v的左孩子是T, 右孩子是RTree
  • 伸展树删除算法(关键码e)
    1. 借助查询算法将关键码e伸展至根节点
    2. 剥离左子树LTree
    3. 只保留原树的右子树: _root被_root->rc替换
    4. 树中查找e,这一步把e的直接后继元素伸展至根节点
    5. 将LTree合并到树中, 合并的方式是位置树的左子树

代码实现

// 引入二叉搜索树
#include "BstTree.h"

template <typename T> class Splay: public BST {
protected:
    // 将节点v伸展至根
    BinNodePosi(T) splay(BinNodePosi(T) v);

public:
    // 查询e,并将最后一个查询到的节点伸展至根
    BinNodePosi(T) &search(T const & e);

    // 插入e,并将e伸展至根
    BinNodePosi(T) insert(T const& e);

    // 删除操作
    bool remove(const& e);
};

// 查询e,并将最后一个查询到的节点伸展至根
template <typename T> BinNodePosi(T) Splay<T>::search(T const & e) {
    // 查找e
  BinNodePosi(T) v =  searchIn(_root, _hot = NULL, e);

  // 无论v->data是不是等于e,都将它伸展至根节点
  return _root = splay(v);
}

// 插入e,并将e伸展至根
template <typename T> BinNodePosi(T) Splay<T>::insert(const T &e) {
    // 处理空树的情况
    if (size() == 0) {
        _size  ++;
        return _root = new BinNode(e, NULL);
    }

    // 查询e
    search(e);

    BinNodePosi(T) t= root();

    // 如果根是e 则不需要后续重组操作
    if (t->data == e) {
        return _root;
    }

    // 大于_root->data 自己建模型就会很清晰了
    if (t->data < e) {
        // 新_root
        t->parent = _root = new BinNode(e, NULL, t, t->rc);

        // t->rc有值的话 需要更新父级节点
        if (HasRChild(*t)) {
            // 修改原右孩子的父级指针
            t->rc->parent = _root;

            // 切断t到原右孩子的指针
            t->rc = NULL;
        }
    } else {
        // 新_root节点
         t->parent = _root = new BinNode(e, NULL, t->lc, t);

         // t如果有左孩子
         if (HasLChild(*t)) {
             // 更新原左孩子的父级指针
             t->lc->parent = _root;

             // 切换t到原左孩子的指针
             t->lc = NULL;

         }

    }


    // 更新高度
    updateHeightAbove(t);
    return _root;
}

// 在节点*p和*lc建立父子关系
template <typename T> inline void attachAsLChild(BinNodePosi(T) p, BinNodePosi(T) lc)
{
    p->lc = lc;
    if (lc) {
        lc->parent = p;
    }
};

// 在节点*p和*rc之间建立父子关系
template <typename T> inline void attachAsRChild(BinNodePosi(T) p, BinNodePosi(T) rc)
{
    p->rc= rc;
    if (rc) {
        rc->parent = p;
    }
};


// 伸展操作, 将节点*v伸展到根节点
template <typename T> BinNodePosi(T) Splay<T>::splay(BinNodePosi(T) v) {
    // 如果*v不能存在 则不需要进行后续伸展操作
    if (!v) {
        return NULL;
    }

    // 声明v的父级节点和祖父节点
    BinNodePosi(T) p, g, gg;


    // 自下而上反复对v进行伸展操作
    while ( (p = v->parent) && (g = p->parent)) {
        // 后面需要定义新v和父级的关系
        gg = g->parent;

        // 建立四种模型
        if (IsLChild(*p)) {
            if (IsLChild(*v)) {
                // zip -- zip
                attachAsLChild(g, p->rc);
                attachAsLChild(p, v->rc);
                attachAsRChild(p, g)
                attachAsRChild(v, p)
            } else {
                // zap -- zip
                attachAsRChild(p, v->lc);
                attachAsLChild(g, v->rc);
                attachAsLC( v, p);
                attachAsRChild(v, g);
            }
        } else {
            if (IsRChild(*v)) {
                // zap -- zap
                attachAsRChild(g, p->lc);
                attachAsRChild(p, v->lc);
                attachAsLChild( p, g);
                attachAsLChild(v, p);
            } else {
                // zip --zap
                attachAsRChild(g, v->lc);
                attachAsLChild(p, v->rc);
                attachAsLChild(v, g);
                attachAsRChild(v, p);
            }
        }

        // 更新gg和v的父子关系
        if (!gg) {
            // 此时v为根节点
            v->parent = NULL;
        } else {
            // 原g是左孩子
            if (gg->lc == g) {
                attachAsLChild(gg, v);
            } else {
                attachAsRChild(gg, v);
            }
        }

        // 更新高度g,p,v的高度 从上面的旋转就可以看出g深度是最深的,p深度次之
        updateHeight(g);
        updateHeight(p);
        updateHeight(v);
    }

    // TODO 还剩下一个单层,需要单独旋转 (建立2中模型)
    if (p) {
        if(IsLChild(*v)){
            // zip
            attachAsLChild(p, v->rc);
            attachAsRChild(v, p);
        }  else {
            attachAsRChild(p, v->lc);
            attachAsLChild(v, p);
        }
        updateHeightAbove(p);
    }

    // 根节点的父亲是NULL
    v->parent = NULL;
    return v;
};

// 从伸展树中删除关键码e
template <typename T> bool Splay<T>::remove(const T &e) {
    // 如果空树或者没有找对关键码则删除失败
    if (!_root) {
        return false;
    }

    if (serach(e)->data != e) {
        return false;
    }

    // 此时_root就是待删除的节点
    BinNodePosi(T) w = _root;

    // 如果没有左子树或者没有右子树可以直接删除_root
    if (!HasLChild(*_root)) {
        _root = _root->rc;
        if (_root) {
            _root->parent = NULL;
        }
    } else if (!HasRChild(*_root)){
        _root = _root->lc;
        if (_root) {
            _root->parent = NULL;
        }
    } else {
        // 左右孩子都有的情况
        // 1. 先分离出来左子树
        // 2. 在删除根节点
        // 3. 在新树中查找e,这样可以将e的直接后继元素伸展至树根
        // 4. 左子树作为新_root的左孩子接入即可

        // 分离左子树
        BinNodePosi(T) lTree = _root->lc;
        _root->lc = NULL;

        // 删除根节点,并将右孩子作为新_root
        _root = _root->lc;
        _root->parent = NULL;

        // 在新树中查找e,这样可以将e的直接后继元素伸展至树根
        serach(e);

        //  左子树作为新_root的左孩子接入即可
        _root->lc = lTree;
        lTree->parent = _root;
    }

    // 释放节点,更新规模
    release(w->data);
    release(w);
    _size --;

    // 更新树根高度
    if (_root) {
        updateHeight(_root);
    }

    return true;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值