伸展树学习总结

伸展树

与AVL树类似, 伸展树也是二叉搜索树的一种形式, 伸展树无需保证时刻保持全树的平衡,也不需要像AVL树一样要求记录平衡因子的附加信息
伸展树的提出源于信息访问的局部性(刚被访问过的信息有可能再次被访问,要被访问的元素可能位于刚访问过的元素的附近), 就伸展树而言,可采用刚被访问的元素移至数据列表的前端,从而降低后续的操作时间

简易伸展树的最坏情况
每次使用search,将访问后的元素移至树根节点, 如果采用单次zig/zag旋转(先对该节点的父节点进行旋转,再对祖父进行旋转,每旋转一次,节点上升一层), 如果总节点数为n,那么总的旋转次数将高达O(n*n), 平摊到每个节点,复杂度(O(n))都高于AVL树, 简易伸展并不会改变当前树的拓扑结构
如图:
这里写图片描述

双层旋转
由于单层旋转将会造成O(n)多,高于AVL树,就此提出改进措施:
进行双层旋转, 每次的旋转不再是从当前节点的parent开始,而是从当前节点的grandpa开始;先对g旋转,再对p旋转,这种旋转方式将在zig-zig,zag-zag下展现效果,如图
这里写图片描述
对比简易伸展与双层旋转,将会发现双层旋转每进行一次search操作, 那么树的高度将会折半,那么如果访问最坏的节点也不会出现O(n)的复杂度,单次操作可在O(logn)时间内完成, 但是如果旋转方式为zig-zag/zag-zig, 双层旋转并不能改变树高,双层旋转将导致树的拓扑结构发生改变
有一种情况为:可能在最后的双层旋转过程中,v只有parent而没有grandpa,但是这种情况只会在最后出现,并且只出现一次,所以并不会影响树的整体旋转效果,因此双层旋转的方式是有效的

伸展树接口定义
基于二叉平衡搜索树类,重写search()(查找过程也会改变树的拓扑结构,所以有必要进行重写), insert(), remove()接口
伸展树的伸展调整算法:

template<typename T>inline void attachAsLChild(NodePosi p, NodePosi lc){
    p->lChild=lc;if(lc)lc->parent=p;
    //为p,lc建立父(左)子的关系,可能lc为null
}
template<typename T>inline void attachAsRChild(NodePosi p, NodePosi rc){
    p->rChild=rc;if(rc)rc->parent=p;
    //为p,lc建立父(左)子的关系,可能lc为null
}

template<typename T>BinNodePosi* Splay<T>::splay(BinNodePosi* v){//从v的位置开始进行伸展操作
    if(!v) return null;
    BinNodePosi* p,g;//v的父亲与祖父
    while((p=v->parent)&&(g=p->parent)){//自下而上进行双层伸展
        BinNodePosi* gg=g->parent;
        if(p->lChild==v)
            if(g->lChild==p){//zig-zig
            //忽略具体旋转过程,直接拼接操作
                attachAsLChild(g,p->rChild);//将g拼接到p上
                attachAsLChild(p,v->rChild);//将p拼接到v上
                attachAsRChild(p,g);
                attachAsRChild(v,p);//操作同上
            }else{//zig-zag
                attachAsLChild(p,v->rChild);attachAsRChild(g,v->lChild);
                attachAsLChild(v,g);attachAsRChild(v,p);
            }
        else if(g->RChild==p){//zag-zag
            attachAsRChild(g,p->lChild);attachAsRChild(p,v->lChild);
            attachAsLChild(p,g);attachAsLChild(v,p);
        } else{//zag-zig
            attachAsRChild(p,v-lChild);attachAsLChild(g,v->rChild);
            attachAsRChild(v,g);attachAsLChild(v,p);
        }
        if(!gg) v->parent=null;//v伸展至最顶端,变为root
        else//曾祖父存在,直接将v作为曾祖父的左孩子或右孩子
            (g==gg->lChild)?attachAsLChild(gg,v):attachAsRChild(gg,v);
        updateHeight(g);updateHeight(p);updateHeight(v);
    }
    if(p=v->parent){//v旋转到最后还有一个p,需要完成一次单旋
        if(p-lChild==v){attachAsLChild(p,v->rChild);attachAsRChild(v,p);}
        else           {attachAsRChild(p,v->lChild);attachAsLChild(v,p);}
        updateHeight(p);updateHeight(v);
    }
    v->parent=null; returnv;//v已移至root位置
}

search实现

//在search过程中,将节点移动
template<typename T>BinNodePosi* Splay<T>::search(const T& e){
    BinNodePosi* p=searchIn(_root,e,_hot=null);//从根节点位置开始查找
    _root=splay((p?p:root));//无论查找是否成功都将返回与之对应的那个节点或者相似的节点至_root位置
    return _root;
}

insert实现

//由于查找的过程中就实现了将要找的相似元素移动至_root位置,再将要插入的元素与root位置的节点作比较,节点重连实现插入操作
template<typename T>BinNodePosi* Splay<T>::insert(const T& e){
    if(!_root) {++_size; return _root= new BinNode<T>(e);}//为空树的情况
    if(e==search(e)->data) return _root;//元素已存在无需插入
    ++_size; BinNodePosi* t=_root;//创建新节点
    if(_root->data<e){//插入e时,以roo为分界线包含root,左边部分t变为e的左孩子,右边部分变为e的右孩子
        t->parent=_root=BinNode<T>(e,null,t,t->rChild);//构造e节点,左右孩子为t,t->rChild
        if(t->rChild) {t->rChild->parent=_root;t->rChild=null;}//将t的rChild的parent由t改为_root,自身连接位置改为null,防止野指针
    }else{
        t->parent=_root=new BinNode<T>(e,null,t->lChild,t);
        if(t->lChild) {t->lChild->parent=_root;t->lChild=null;}
        //原理同上
    }
    updateHeight(t);//更新祖先高度
    return _root;//无论e是否在原树中,返回值_root->data总是等于e
}

删除实现

原理与insert一致,当进行完search操作后,位于root位置的则是要删除的元素或者是相似的元素,在root的左子树内查找要删除元素的直接后继succ()(表示二叉树中序遍历序列列表)将其替代即可完成删除(这步操作与平衡搜索树的remove一致),保证树的局部性

template<typename T>bool Splay<T>::remove(const T& e){
    if(!_root||(e!=search(e)->data)) return false;//查找的节点信息未在树中
    BinNodePosi* w=_root;//要删除的节点已经伸展至树根位置
    if(!_root->lChild) {//若无左子树
        _root=_root->rChild; if(_root) _root->parent=null;
    }else if(!_root->rChild){//无右子树
        _root=_root->lChild; if(_root) _root->parent=null;
    }else{//左右子树都存在
        BinNodePosi* lTREE=_root->lChild;
        lTREE->parent=null;_root->lChild=null;//将左子树暂时切除
        _root=_root->rChild;_root->parent=null;
        search(w->data);//该查找必定失败,但是也将右子树中的最小节点伸展至root的位置
        //该过程也可调用直接后继,根据中序遍历在右子树中查找最小节点
        _root->lChild=lTREE;lTREE->parent=_root;//将左子树重新归位
    }
    delete w;_size--;//释放要删除的节点
    if(_root) updateHeight(_root);//树非空时,更新高度
    return true;
}

总结:伸展树复杂度与AVL树一致O(logn),局部性较强,平均效率较高, 但是针对最坏情况下的单次操作时任需要O(n), 不适用于对效率敏感的场所

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值