赞
踩
节点v一旦被访问,随即转移至树根。
自下而上,逐层单旋
效率低:
(1)若全树的拓扑结构始终呈现单链条结构,那就等价于一维列表
(2)被访问节点的深度,呈现周期性的算术级数的演变,平均为Ω(n)
向上追溯两层而非一层
zigzag/zagzig与AVL树双旋完全等效
zigzig/zagzag重点是这个!先旋转祖父节点!
zig/zag若没有祖父节点,此时必然有parent(v)==root(T),仔细思考可得,这种情况至多出现一次,且是在最后出现
接口:
#include"BST.h"
template<class T> class Splay:public BST<T>{
protected:
BinNodePosi(T) splay(BinNodePosi(T) v);//将v伸展至树根
public:
BinNodePosi(T) & search(const T & e);
BinNodePosi(T) insert(const T & e);
bool remove(const T & e);
};
伸展算法:
1.查找:
template<class T>
BinNodePosi(T) & Splay<T>::search(const T & e){
BinNodePosi(T) p=searchIn(this->_root,e,this->_hot=0);
this->_root=splay(p?p:this->_hot);
return this->_root;
}
内部接口:BinNodePosi(T) splay(BinNodePosi(T) v);这里的旋转看图会更好理解
template<class T> BinNodePosi(T) Splay<T>::splay(BinNodePosi(T) v){ if(!v) return 0; BinNodePosi(T) p;BinNodePosi(T) g; while((p=v->parent)&&(g=p->parent)){//自下而上。反复双层伸展 BinNodePosi(T) gg=g->parent;//每轮之后,v都将以曾祖父为父 if(IsLChild(*v))//zigzig if(IsLChild(*p)){ attachAsLChild(g,p->rc); attachAsLChild(p,v->rc); attachAsRChild(p,g); attachAsRChild(v,p); } else{//zigzag connect34(p,v,g,p->lChid,v->lChild,v->rChild,g->rChild); } else if(IsLChild(*p)){//zagzig connect34(g,v,p,g->lChild,v->lChild,v->rChild,p->rChild); } else{//zagzag attachAsRChild(g,p->lc); attachAsRChild(p,v->rc); attachAsLChild(p,g); attachAsLChild(v,p); } if(!gg) v->parent=0; else (g==gg->lc) ? attachAsLChild(gg,v):attachAsRChild(gg,v); updateHeight(g);updateHeight(p);updateHeight(v); }//双层伸展结束时必有g==null,p可能为非空 if(p=v->parent){//p为根节点,只需单旋至多一次 if(IsLChild(*v)){ attachAsLChild(p,v->rc); attachAsRChild(v,p); } else{ attachAsRChild(p,v->lc); attachAsLChild(v,p); } } v->parent=0; return v; }
2.插入:
3.删除:
首先得明确为什么要使用B_Tree,在多级存储系统中使用B_Tree,可针对外部查找,大大减少I/O次数,充分利用外存对批量访问的高效支持,每下降一层都以超级节点为单位,读入一组关键码。
BTNode:
#define BTNodePosi(T) BTNode<T>*
template <class T> struct BTNode{
BTNodePosi(T) parent;
vector<T> key;//数值向量
vector<BTNodePosi(T)> child;//孩子向量(其长度比key多1)
BTNode(){parent=0;child.insert(0,0);} //构造空节点
BTNode(T e,BTNodePosi(T) lc=0,BTNodePosi(T) rc=0){
parent=0;
key.insert(0,e);
child.insert(0,lc);child.insert(0,rc);
if(lc) lc->parent=this;
if(rc) rc->parent=this;
}
};
BTree:
template <class T> class BTree{
protected:
int _size;int _order;BTNodePosi(T) _root;//关键码总数,阶次,根
BTNodePosi(T) _hot;//查找接口最后访问的非空节点位置
void solveOverflow(BTNodePosi(T));//因插入而产生的上溢
void solveUnderflow(BTNodePosi(T));//因删除而产生的下溢
public:
BTNodePosi(T) search(const T & e);
bool insert(const T & e);
bool remove(const T & e);
};
根据实例来看可以更好地理解:
实现:
template <class T>
BTNodePosi(T) BTree<T>::search(const T & e){
BTNodePosi(T) v=_root;_hot=0;//从根节点出发
while(v){//逐层查找
Rank r=v->key.search(e);//在当前节点对应的向量中顺序查找
if(0<=r&&e==v->key[r]) return v;
_hot=v;
v=v->child[r+1];//沿引用转至对应的下层子树,并载入其根I/O
} //若因为!v而退出,则意味着抵达外部节点
return 0;//失败
}
bool BTree<T>::insert(const T & e){
BTNodePosi(T) v=search(e);//b树的查找接口
if(v) return false;
Rank r=_hot->key.search(e);//vector的查找接口
_hot->key.insert(r+1,e);
_hot->child.insert(r+2,0);//也可以在child向量最后插入,反正那一层都是空节点
_size++;
solveOverflow(_hot);
return true;
}
插入后考虑各节点的上溢情况,即分支数大于m
思路:
创建新树根使树高增高的可能性非常低
实现:
template <class T> void BTree<T>::solveOverflow(BTNodePosi(T) v){ if(_order>=v->child.size()) return;//递归基:不再上溢时 Rank s=_order/2;//轴点(此时_order=key.size()=child.size()-1) BTNodePosi(T) u=new BTNode<T>(); for(Rank j=0;j<_order-s-1;j++){//分裂出右侧节点u(效率低可改进) u->child.insert(j,v->child.remove(s+1));//v右侧_order-s-1个孩子 u->key.insert(j,v->key.remove(s+1));//v右侧_order-s-1个关键码 } u->child[_order-s-1]=v->child.remove(s+1);//移动v最靠右的孩子 if(u->child[0])//若u的孩子为非空,则统一令其以u为父节点 for(Rank j=0;j<_order-s;j++) u->child[j]->parent=u; BTNodePosi(T) p=v->parent;//v当前的父节点 if(!p){//若p为空,则创建(全树长高一层,新节点恰好两度 _root=p=new BTNode<T>(); p->child[0]=v; v->parent=p; } Rank r=1+p->key.search(v->key[0]);//轴点关键码上升 p->key.insert(r,v->key.remove(s) ); p->child.insert(r+1,u); u->parent=p;//新节点与父亲点p互联 solveOverflow(p); }
bool BTree<T>::remove(const T & e){ BTNodePosi(T) v=search(e);//b树的查找接口 if(v) return false; Rank r=_hot->key.search(e);//vector的查找接口 if (v->child[0]){//若v非叶子,即它的下一层还有数据 BTNodePosi(T) u=v->child[r+1];//在右子树中一直向左,即可 while(u->child[0]) u=u->child[0];//找到e的后继 v->key[r]=u->key[0]; v=u;r=0;//与之交换位置 }//至此,v必然在最底层,且其中第r个关键码就是待删者 v->key.remove(r); v->child.remove(r+1); _size--; solveUnderflow(v); return true; }
删除后考虑下溢的情况,即
删除树根使树高降低的可能性非常低
实现:
template <class T> void BTree<T>::solveUnderflow(BTNodePosi(T) v){ if((_order+1)/2<=v->child.size()) return;//递归基:v并未下溢时 BTNodePosi(T) p=v->parent; if(!p){//递归基:已经到根节点 _root=v; } Rank r=0;while(p->child[r]!=v) r++;//确定v是p的第r个孩子 //情况1:若v的左孩子存在,且至少包含[m/2]个关键码 if(0<r){//若v不是p的第一个孩子,则 BTNodePosi(T) ls=p->child[r-1];//左兄弟必然存在 if((_order+1)/2<=ls->child.size()){//若该兄弟够胖,则 v->key.insert(0,p->key[r-1]);//p借出一个关键码给v(作为最小关键码) p->key[r-1]=ls->key.remove(ls->key.size()-1);//ls的最大关键吗转入p v->child.insert(0,ls->child.remove(ls->child.size()-1));//同时ls的最右侧孩子过继给v(作为v的最左侧孩子) if(v->child[0]) v->child[0]->parent=v; return; } } //情况2:若v的右孩子存在,且至少包含[m/2]个关键码 if(p->child.size()-1>r){ BTNodePosi(T) rs=p->child[r+1];//右兄弟必然存在 if((_order+1)/2<=rs->child.size()){//若该兄弟够胖,则 v->key.insert(v->key.size()-1,p->key[r]);//p借出一个关键码给v(作为最大关键码) p->key[r]=rs->key.remove(0);//rs的最小关键吗转入p v->child.insert(v->key.size()-1,rs->child.remove(0));//同时rs的最左侧孩子过继给v(作为v的最右侧孩子) if(v->child[v->key.size()-1]) v->child[v->key.size()-1]->parent=v; return; } } //情况3: if(0<r){//与左孩子合并 BTNodePosi(T) ls=p->child[r-1];//左兄弟必然存在 ls->key.insert(ls->key.size(),p->key.remove(r-1)); p->child.remove(r);//p的第r-1个关键码转入ls,v不再是p的第r个孩子 ls->child.insert(ls->child.size,v->child.remove(0)); if(ls->child[ls->child.size()-1])//v的最左侧孩子过继给ls做最右侧孩子 ls->child[ls->child.size()-1]->parent=ls; while(!v->key.empty()){//v剩余关键码和孩子,依次转入ls ls->key.insert(ls->key.size(),v->key.remove(0)); ls->child.insert(ls->child.size(),v->key.remove(0)); if(ls->child[ls->child.size()-1]) ls->child[ls->child.size()-1]->parent=ls; } } else{//与右孩子合并 BTNodePosi(T) rs=p->child[r+1]; rs->key.insert(0,p->key.remove[r]); p->child.remove(r+1); rs->child.insert(0,v->child.remove(v->child.size()-1)); if(rs->child[0]) rs->child[0]->parent=rs; while(!v->key.empty()){//v剩余关键码和孩子,依次转入rs rs->key.insert(0,v->key.remove(v->key.size()-1)); rs->child.insert(0,v->child.remove(v->child.size())); if(rs->child[0]) rs->child[0]->parent=rs; } } solveUnderflow(p); return; }
由于树的结构在不断更新,且就树形结构的拓扑而言,相邻版本之间的差异(差异来自于旋转操作)不能超过O(1)(即旋转操作不能超过常数次),但是绝大多数的BBST却不能保证这一点。
而红黑树的任何一次操作引发的结构变化量不致超过常数次,它通过给各个节点赋予红或黑色,使树相对平衡,具有持久性
提升变换:将红节点上升与其父节点平行(所有底层节点高度变为一致)
这里出现了[Error] expected template-name before ‘<’ token,后来发现是没有#include "BST.h"导致,真傻
template <class T> class RedBlackL:public BST<T>{
public:
//BST的查找等接口可以沿用
BinNodePosi(T) insert(const T & e);//重写插入操作
bool remove(const T & e);//删除
protected:
void solveDoubleRed(BinNodePosi(T) x);//双红修正
void solveDoubleBlack(BinNodePosi(T) x); //双黑修正
int updateHeight(BinNodePosi(T) x);//更新节点高度
};
对于红黑树而言,高度指的是黑高度!!!
template <class T>
int RedBlack<T>::updateHeight(BinNodePosi(T) x){
x->height=max(stature(x->lr),statue(x->rc));
if(IsBlack(x)) x->height++;return x->height;//只计入黑节点
}
解释:#define stature(x) ((x) ? (x)->height : -1)
可以看到这里的插入结果满足红黑树的三个条件:12:树根及外部节点均为黑色 4:黑节点数目没变,所以各节点的黑高度不变
但是却不满足: 3:红节点只能有黑孩子
template <class T>
BinNodePosi(T) RedBlack<T>::insert(const T & e){
BinNodePosi(T) & x=search(e);
if(x) return x;//目标节点存在
x=new BinNode<T>(e,this->_hot,0,0,-1);//创建红节点,以_hot为父,黑高度-1
this->_size++;
solveDoubleRed(x);//如有必要,做双红修正
return x?x:this->_hot->parent;
}//无论原树中是否存在e,返回时总有x->data==e
因为插入的红节点x其父亲的颜色不明,所以这里视叔父u的颜色分为两种情况:
(1)u为黑色(这里只列出了两种情况)
可以看到,此时将红黑树提升变换后,一个超级节点中出现了两个紧邻的红色的节点,此时只需将局部做一个3+4重构,将父节点染黑,两个孩子染红即可
(2)u为红色(调整过程b->b’->c’->c)
可以看到,此时将红黑树提升变换后,一个超级节点中出现了四个节点!等效于发生了上溢,直接分裂,类似于B_Tree的上溢处理
template <class T> void RedBlack<T>::solveDoubleRed(BinNodePosi(T) x){ if(IsRoot(*x)){//若已经递归至树根,则将其转黑,整棵树的高度也随之递增 this->_root->color=RB_BLACK; this->_root->height++; return; }//否则 BinNodePosi(T) p=x->parent; if(IsBlack(p)) return;//考察x的父亲的颜色,为黑则终止调整 BinNodePosi(T) g=p->parent; BinNodePosi(T) u=uncle(x);//以下视叔父u的颜色分别处理 if(IsBlack(u)){//u为黑节点,3+4重构,父节点染黑,孩子节点染红 if(IsLChild(*x)==IsLchild(*p)) p->color=RB_BLACK;//x与p同侧,p由红转黑,x保持红, else x->color=RB_BLACK;//否则,x由红转黑,p保持红 g->color=RB_RED;//g必然由黑转红 BinNodePosi(T) gg=g->parent; BinNodePosi(T) r=FromParentTo(*g)=rotatrAt(x); r->parent=gg; } else{ p->color=RB_BLACK;p->height++;//p由红转黑,增高 u->color=RB_BLACK;u->height++;//u由红转黑,增高 if(!IsRoot(*g)) g->color=RB_RED;//若g非根则转红 solveDoubleRed(g);//继续调整g(类似于尾递归,可优化) } }
这里的r将替换x,(a)和(b)的情况很简单,直接将r节点染黑即可,此时的黑高度也不会变化,但是(c)的情况就比较复杂了,可以看到删除后,黑深度不再统一,对红黑树做提升处理,x成为一个单独的节点,发生下溢
template <class T> bool RedBlack<T>::remove(const T & e){ BinNodePosi(T) & x=search(e); if(!x) return false;//查找定位 BinNodePosi(T) r=removeAt(x,this->_hot);//删除——hot的某孩子,r直接指向其接替者 if(!(--this->_size)) return;//若删除后为空树,则直接返回 if(!this->_hot){//若被删除者是根(此时的_root就是x),则 this->_root->color=RB_BLACK;//将其染为黑色,并 updateHeight(this->_root); return true; }//至此,原x(现r)必非根 if(BlacHeightUpdated(*this->_hot)) return true;//若父亲及祖先依然平衡,则无需调整 //至此,必然失衡 if(IsRed(r)){//若替代节点为红,则只需简单的翻转其颜色 r->color=RB_BLACK; r->height++; return true; } //至此,r以及被其替代的x均为黑色 solveDoubleBlack(r);//双黑调整(入口必有r==null) return true; }
这里针对r的父亲以及r的兄弟的颜色,分为四种情况
(1)BB_1:兄弟s为黑,且至少有一个红孩子t,通过a和b映射的B_Tree来理解(类似于B_Tree的旋转操作)
(2)BB_2R:兄弟s为黑,且两个孩子均为黑;p为红(类似于B_Tree的合并操作)
(3)BB_2B:兄弟s为黑,且两个孩子均为黑;p为黑(这里的s变红才可以和p合并)
(4)BB_3:兄弟s为红,其余孩子均为黑
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。