您的位置:首页 > 其它

伸展树

2017-08-23 16:50 106 查看

伸展树

AVL树

优点: 是对任何动态操作仅需要O(logn)的时间。空间复杂度也只占O(n)的空间。

缺点:· 构造AVL树需要借助高度或平衡因子,为此需要改造元素结构,或是额外封装。

· 插入/删除动态操作引发的旋转,成本高昂。

· 删除节点后引发的旋转(失衡传播)最坏的情况下需要遍历整树的高度。

· 不适合频繁进行动态操作的场合。

· 全树的拓布结构一旦发生插入/删除操作可能发生巨大的改变。

为此,考虑一种便于实现的而且对于频繁插入/删除操作的不敏感的数据结构——伸展树。

高级搜索树的不同之处就在于局部性的定义不同而诞生出的各个版本。

参考AVL树的局部性。伸展树的局部性在于无需时刻都严格地保持全树平衡,但却能够在任何足够长的真实操作序列中,保持分摊意义上的高效率。

而且,伸展树还采用了数据局部性的原理:

· 刚刚被访问过的元素,极有可能在不久之后再次被访问到。

· 将被访问的下一元素,极有可能处于前不久被访问过元素附近。

采用这一策略就可以大致得到伸展树的思路。两种策略将被访问到的节点转移至树根。

· 逐层伸展

· 双层伸展

逐层伸展:

无非是对当前节点进行一系列的zig/zag(v->parent)旋转。

而伸展效率要视树的初始形态和节点访问次序所确定。

不妨简单做个估算,一层一层的上升,设节点总数为n,那么旋转操作的次数为:

n-1+n-2+n-3+…+1成算数级数递增,因此需要O(n^2)的复杂度。

如果发生了仅有左孩子的情况,此时伸展树将会发生最坏的状况。

在反复查询之后,可能又会重新回到初始的状况。

双层伸展:

每次都从节点v向上追溯两层。

并根据父亲节点p以及祖父g的相对位置进行旋转。

与AVL树双旋完全一致,但是,对zig/zig以及zag/zag旋转出现了调整。

g->lc=p;p->lc=v;这种左侧分支的情况,在AVL树中做两次旋转调整zig(v)和zag(p)旋转。

而此时的调整策略是zig(g)/zig(p)。右侧情况则完全对称。

如果v没有祖父,只有父亲。//if(!g)..

则此时必有v->parent = _root;在做一次单旋即可。

ADT接口及定义

#include "..BST/BST.h"
template<typename T>class Splay:public BST<T>{
protected:
BinNodePosi(T) splay(BinNodePosi(T) x);
public:
BinNodePosi(T) & search(const T & e);
BinNodePosi(T) insert(const T & e);
bool remove(const T & e);
}


伸展算法

template<typename T>BinNodePosi(T) Splay<T>::splay(BinNodePosi(T) v){
if(!v) return v;
BinNodePosi(T) p;//v的父亲
BinNodePosi(T) g;//v的祖父
while((p = v->parent) && (g = p->parent)){//自下而上,反复双层伸展
BinNodePosi(T) gg = g->parent;//每一次伸展,v都已原祖父为父亲
if(IsLChild(*p))//根据p的位置
if(IsLChild(*v)){/*zig(g)-zig(p)*/}
else {/*zag(v)-zig(v)*/}
else if(IsRChild(*v)){/*zag(g)-zag(p)*/}
else{/*zig(v)-zag(v)*/}
if(!gg) v->parent = NULL;//如果gg不存在,即v已到达根部
else (g==gg->lc)? LeftConnect(gg,v):RightConnect(gg,v);//否则,必然是出现v没有祖父确有父亲的情况
updateHeight(g);
updateHeight(p);
updateHeight(v);
}
if(p = v->parent){//如果p真是根,再做一次单旋即可
if(IsLChild(*v)) LeftConnect(p,v->rc);RightConnect(v,p);
else RightConnect(p,v->lc);LeftConnect(v,p);
}
v->parent = NULL;//伸展完成,v抵达树根
return v;
}


查找算法?不再是静态操作

template<typename T>BinNodePosi(T) & Splay<T>::search(const T & e){
BinNodePosi(T) x = searchIn(_root,e,_hot=NULL);//调用BSTsearIn接口,注意语义规范
_root = splay(x?x:_hot);//不论成功与否,总能返回
return _root;
}


插入算法

· 直观的去考虑调用BST::insert()接口插入,将其伸展至根。

· 而Splay::search()接口已包含伸展算法。

执行一次必然失败的查找

若将最后所搜索到的节点设为t,将其伸展至根,左右子树分别记为tl,tr

设t < e,则将t的右子树断开,将v接入其中,并成为根,t为其左孩子,tr为其右孩子

template<typename T>BinNodePosi(T) Splay<T>::insert(const T & e){
//根不存在,也就是退化情况,创建一个根节点即可
if(!_root){_size++
4000
;_root = new BinNode<T>(e,NULL,NULL,NULL);}
if(e==search(e)->data) return _root;//如果目标节点存在直接返回即可
else{
BinNodePosi(T) t = _root;//创建新节点
if(_root->data < e){//如果e在被查找节点的右侧,根据search接口的规范,t必须是e的直接后继
_root = new BinNode<T>(e,NULL,,t,t->rc);//t<--v--t->tr的形式创建新的节点
t->parent = _root;
if(HasRChild(*t)){t->rc->parent = _root;t->rc = NULL;}//如果t存在右子树,则剔除
}
//完全对称的情况
else if(e<_root->data){
_root = new BinNode<T>(e,NULL,t->lc,t);
t->parent = _root;
if(HasLChild(*t)){t->lc->parent = _root;t->lc = NULL;}
}
}
updateHeightAbove(t);//更新树高
return _root;
}


删除算法

依照以上思路先调用BST::remove()接口,在经过双层伸展折叠至树根。

既然search()接口已经集合了splay()接口,先search(e),这是v已是根节点,将v摘除。

令vl与vr分别是摘除v之后的左右子树,在左子树中,执行一次必然失败的search(e)根据接口语义规范。

会将m=v->pred()提升至树根,而根据二叉树的顺序性,m的右子树必然为空,而且vr中所有节点关键码必然大于m

template<typename T>bool Splay<T>::remove(const T & e){
//处理退化情况及被删除节点不存在
if(!_root||!e==search(e)->data) return false;
BinNodePosi(T) w = _root; //search之后e已达树根
if(!HasLChild(*_root)){_root = _root->rc;if(_root) _root->parent = NULL;}//只有右子树
else if(!HasRChild(*_root)){_root = _root->lc;_root->parent = NULL;}//只有左子树
else{//保留右子树
BinNodePosi(T) Rtree = _root->rc;
Rtree->parent = NULL;
//切除右子树
_root->rc= NULL;
_root = _root->lc;//只保留左子树
_root->parent=NULL;
search(w->data);//一次必定失败的查找
_root->rc = Rtree;Rtree->parent = _root;//将右子树接入
}
release(w->data);release(w);_size--;
if(_root) updateHeight(_root);
return true;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  伸展树