8.1 伸展树
作者:互联网
一、伸展树
- 引入伸展树的最初动机:具体来说,也就是试图利用所谓的局部性
- 局部性(Locality):刚被访问过的数据,极有可能很快地再次被访问,这一现象在信息处理过程中数见不鲜
- 局部性的BST描述:刚刚访问过的节点,极有可能很快地再次被访问。下一将要访问的节点,极有可能就在刚被访问过的节点的附近
- 连续的m次查找(m >> n = |BST|), 采用AVL共需O(mlogn)时间
- 仿照自适应链表,利用局部性,能否更快?
- 逐层伸展
- 节点v一旦被访问,随即转移至树根(通过左旋和右旋使之上升)
- 自下而上,逐层单旋
- zig( v->parent)
- zag(v->parent)
- 直到v最终被推送至树根
- 根据最坏情况,对该策略进行改进
- 双层伸展
- 构思的精髓:向上追溯两层,而不是一层
- 反复考察祖孙三代:g = parent§, p = parent(v), v
- 根据它们的相对位置,经两次旋转,使得v上升两层,成为(子)树根
- zig - zag / zag - zig
- 与AVL树双旋完全等效,与逐层伸展别无二致
- 与AVL树双旋完全等效,与逐层伸展别无二致
- zig - zig / zag - zag
- 折叠效果:一旦访问坏节点,对应发路径长度随即减半,最坏情况不致持续发生
- 单趟伸展操作,分摊O(logn)时间
- 实现:伸展树接口
template<typename T>
class Splay:public BST<T>{//由BST派生
protected:BinNodePosi(T) splay(BinNodePosi(T) v);//将v伸展至根
public://伸展树的查找也会引起整树的结构调整,故search()也需要重写
BinNodePosi(T) & search(const T & e);//查找(重写)
BinNodePosi(T) insert(const T & e);//插入(重写)
bool remove(const T& e);//删除(重写)
};
- 实现:伸展算法
template <typename T> BinNodePosi(T) v){
if(!v) return NULL; BinNodePosi(T) p; BinNodePosi(T) g;//父亲、祖父
while((p = v->parent) && (g = p->parent)){//自下而上,反复双层伸展
BinNodePosi(T) gg = g->parent;//每轮之后,v都将以原曾祖父为父
if(IsLChild(*v))
if(LsLChild(*p)){/*zig - zig*/}else{/* zig - zag*/}
else if(IsRChild(*p)){/*zag - zag*/}else{/*zag - zig*/}
//每经过一次双层伸展,我们都需要将局部这棵新的子树接入到原树中对应 的位置,并更新相应节点的高度信息
if(!gg) v->parent = NULL;//若无曾祖父gg,则v现即为树根;否则,gg此后应为以v为左或右孩子
else(g == gg->lc)?attachAsLChild(gg, v):attachAsRChild(gg, v);
updateHeight(g); updateHeight(p); updateHeight(v);
}//双层伸展结束时,必有g == NULL,但p可能非空
if(p = v->parent){/*若p为根,只需在额外单旋(至多一次)*/}
v->parent = NULL; return v;//伸展完成,v抵达树根
}
//使用直接拼接的方式实现左旋或右旋
if(IsLChild(*v))
if(IsLChild(*p)){
attachAsLChild(g, p->rc);//p的右孩子当作g的左孩子
attachAsLChild(p, v->rc);
attachAsRChild(p, g);
attachAsRChild(v, p);
}else{/*zig - zag*/}
else
if(IsRChild(*p)){/*zag - zag*/}
else{/*zag - zag*/}
- 查找接口:伸展树的查找操作,与常规BST::search()不同,很可能会改变树的拓扑结构,不再属于静态操作
template <typename T> BinNodePosi(T) & Splay<T>::search(const T& e){
//调用标准BST的内部接口定位目标节点
BinNodePosi(T) p = searchIn(_root, e, _hot = NULL);
//无论成功与否,最后被访问的节点都将伸展至根
_root = splay(p ? p : _hot);
//总是返回根节点
return _root;
}
- 插入算法
- 重写后的Splay::search()已集成了splay()操作,查找失败后,_hot即是根节点
- 直接在根节点附近插入目标节点
- 删除算法
- Splay::search()查找(成功)之后,目标节点即是树根
- 在树根附近完成目标节点的删除
- 找到右子树中的最小者,将其伸展到右子树根节点的位置,同时连接左子树,就能获得删除目标节点后的伸展树
- 综合评价
- 无需记录节点高度或平衡因子;编程实现简单易行——优于AVL树;分摊复杂度O(logn)——与AVL树相当
- 局部性强,缓存命中率极高时(即k << n << m)
- 效率可以更高——自适应的O(logk)
- 任何连续的m次查找,都可在O(mlogk + nlogn)时间内完成
- 仍不能保证单次最坏情况的出现,不适应于对效率敏感的场合
标签:8.1,parent,BinNodePosi,zag,zig,节点,伸展 来源: https://blog.csdn.net/weixin_45781228/article/details/119087549