第七章 二叉搜索树

(a)概述

循关键码访问:数据项之间,依照各自的关键码彼此区分(call-by-key),条件是关键码之间支持大小比较与相等比对,数据集合中的数据项统一地表示和实现为词条entry形式。
词条

template <typename K, typename V> struct Entry{//词条模板类K key; V value;Entry( K k = K(), V v = V()): key(k), value(v){}; //默认构造函数Entry( Entry<K, V> const & e):key(e.key),value(e.value){};//克隆//比较器、判等器(从此,不必严格区分词条及其对应的关键码) bool operator< ( Entry<K,V> const & e ){ return key < e.key;} //小于bool operator> ( Entry<K,V> const & e ){ return key > e.key;} //大于bool operator==( Entry<K,V> const & e ){ return key == e.key;} //等于bool operator!=( Entry<K,V> const & e ){ return key!= e.key;}//不等
}

二叉搜索树:节点~ 词条~ 关键码
顺序性:任一节点均不小于其左后代,不大于其右后代,等效于任一节点均不小于/不大于其左/右孩子。为简化起见,禁止重复词条,这种简化应用中不自然,算法上无必要。(顺序性虽然只是对局部特征的刻画,但由此却可导出某种全局特征)
单调性:BST的中序遍历序列,必然单调非降,这一性质也是BST的充要条件。
BST模板类

template <typename T> class BST:  public BinTree<T>{//由BinTree派生
public: //以virtual修饰,以便派生类重写virtual BinNodePosi(T) & search( const T & ); //查找virtual BinNodePosi(T) insert( const T & ); //插入virtual bool remove( const T & );//删除
protected:BinNodePosi(T) _hot; //命中节点的父亲BinNodePosi(T) connect34( //3+4重构BinNodePosi(T), BinNodePosi(T), BinNodePosi(T),BinNodePosi(T), BinNodePosi(T), BinNodePosi(T),BinNodePosi(T);BinNodePosi(T) rotateAt( BinNodePosi(T) );//旋转调整)
}

(b)算法及实现

查找算法:从根节点出发,逐步地缩小查找范围,直到发现目标(成功),或查找范围缩小至空树(失败),对照中序遍历序列可见,整个过程可视作是在仿效有序向量的二分查找
实现

template <typename T> BinNodePosi(T) & BST<T>::search(const T & e)
{return searchIn( _root, e, _hot = NULL); } //从根节点启动查找
static BinNodePosi(T) & searchIn(BinNodePosi(T) & v, //当前子根const T & e, //目标关键码BinNodePosi(T) & hot) //记忆热点
{if (!v || (e == v-> data)) return v; //足以确定失败、成功hot = v; //记下当前非空节点return searchIn(((e < v->data) ? v->lChild : v->rChild),e,hot);
}//运行时间正比于返回节点v的深度,不超过树高O(h)

查找:接口语义
返回的引用值:成功时,指向一个关键码为e且真实存在的节点;失败时,指向最后一次试图转向的空节点NULL,不妨假想此空节点,转换为一个数值为e的哨兵节点,如此依然满足BST的充要条件。
无论成功与否,返回值总是等效地指向命中节点,而**_hot**总是指向命中节点的父亲。
插入算法:先借助search(e)确定插入位置及方向,再将新节点作为叶子插入。若e尚不存在,则 _hot为新节点的父亲,v = search(e)为_hot对新孩子的引用。
实现

template <typename T> BinNodePosi(T) BST<T>::insert( const T & e ){BinNodePosi(T) & x = search(e); //查找目标(留意_hot的位置)if ( !x ){ //禁止雷同元素,仅在查找失败时才实施插入操作x = new BinNode<T>(e,_hot); //在x处创建新节点,以_hot为父亲_size++; updateHeightAbove(x); //更新全树规模,更新x及其历代祖先的高度}return x; //无论e是否存在于原树中,总有x->data == e
} //O(h) 对于首个节点插入之类的边界情况,均可正确处置

删除算法

template <typename T> bool BST<T>::remove( const T & e){BinNodePosi(T) & x = search(e); //定位目标节点if ( !x ) return false; //确认目标存在(此时_hot为x的父亲)removeAt( x, _hot); //分两大类情况实施删除,更新全树规模_size--; //更新全树规模updateHeightAbove( _hot ); //更新_hot及其历代祖先的高度return true;
}//删除成功与否,由返回值指示

删除:情况一
若x的某一子树为空,则可将其替换为另一子树,如此操作之后二叉树的拓扑结构依然完整,顺序性依然满足。

template <typename T> static BinNodePosi(T) removeAt( BinNodePosi(T) & x, BinNodePosi(T) & hot){BinNodePosi(T) w = x; //被摘除的节点BinNodePosi(T) succ = NULL; //被删除节点的接替者if (!HasLChild( *x )) succ = x = x->rChild; //左子树为空else if (!HasRChild( *x )) succ = x = x->lChild; //右子树为空else{/*...左右子树并存...*/}hot = w->parent; //被删除节点的父亲if (succ) succ->parent = hot; //将被删除节点的接替者与hot相联release(w->data); release(w); //释放被删除的节点return succ; //返回被接替者
} // O(1)

删除:情况二
被删除节点两子树都不为空。

template <typename T> static BinNodePosi(T) removeAt( BinNodePosi(T) & x, BinNodePosi(T) & hot){/*......*/else{w = w->succ(); swap( x->data, w->data); //令*x与其后继*w互换数据BinNodePosi(T) u = w->parent; //原问题转化为摘除非二度节点w( u==x ? u->rChild : u->lChild )= succ = w->rChild;}/*......*/
}

(c)平衡与等价

BST = Vector + List
n个节点的BST若随机组成成,有n!种(catalan(n)),若能缩小到O(logn),甚至O(n^(1/2)),则理想。
理想平衡:节点数目固定时,兄弟子树高度越接近平衡,全树也将倾向于更低,由n个节点组成的二叉树,高度不低于[log2n],恰好为[log2n]时称作理想平衡(CBT)
适度平衡:理想平衡出现概率低,维护成本高,可适当放松标准,当高度渐进地不超过O(logn),可称作适度平衡,适度平衡的BST称作平衡二叉搜索树(BBST)
等价BST:上下可变(联结关系不尽相同,承袭关系可能颠倒),左右不乱(中序遍历序列一致,全局单调非降)。
等价变换+旋转调整:zig(v),zag(v)

(d)AVL树

平衡因子:balFac(v) = height( lc(v) ) - height( rc(v) ),对任意的v,|balFac(v)|≤1
AVL = 适度平衡
(1)height(AVL) = O(logn),n = Ω( 2 ^ height(AVL) )
(2)高度为h的AVL树,至少包含S(h) = fib(h+3) - 1个节点。数学归纳:S(h) = 1 + S(h - 1) + S(h - 2),令T(h) = S(h) + 1,则T(h) = T(h-1) + T(h-2) = fib(h+3)。
AVL:接口

#define Balanced(x) \ //理想平衡(stature((x).lChild)==stature((x).rChild))
#define BalFac(x) \ //平衡因子(stature((x).lChild)-stature((x).rChild))
#define AvlBalanced(x) \ //AVL平衡条件((-2 < BalFac(x))&&(BalFac(x)<2))
template <typename T> class AVL: public BST<T>{public: //BST::search()等接口可直接沿用BinNodePosi(T) insert( const T & ); //插入重写bool remove( const T & );//删除重写
}

插入:单旋(zigzig / zagzag,同时可能有多个失衡节点,最低者g不低于x祖父,g经单旋调整后复衡,子树高度复原;更高祖先也必平衡,全树复衡);
插入:双旋(zigzag/zagzig)
插入:实现

template <typename T> BinNodePosi(T) AVL<T>::insert( const T & e){BinNodePosi(T) & x = search(e);if(x) return x; //若目标尚不存在x = new BinNode<T>(e,_hot); size_++; BinNodePosi(T) xx = x; //则创建x//从x的父亲出发逐层向上,依次检查各代祖先gfor ( BinNodePosi(T) g = x->parent; g; g = g->parent)if (!AvlBalanced(*g)){//一旦发现g失衡FromParentTo(*g)=rotateAt(tallerChild(tallerChild(g)));break;//g复衡后局部子树高度必复原,其祖先也如此}else//否则(在依然平衡的祖先处)updateHeight(g);//更新其高度(平衡性虽不变,高度却有可能改变)return xx;//返回新节点:至多只需一次调整
}

删除:单旋(同时至多一个失衡节点g,首个可能就是x的父亲_hot,g经单旋调整后复衡,子树高度未必复原,更高祖先仍可能失衡,因有失衡传播现象,可能需要做O(logn)次调整)
删除:实现

template <typename T> bool AVL<T>::remove( const T & e ){BinNodePosi(T) & x = search(e); if(!x) return false; //若目标的确存在removeAt(x,_hot);size--; //在按BST规则删除后,_hot及祖先均有可能失衡//从_hot出发逐层向上,依次检查各代祖先gfor (BinNodePosi(T) g =_hot;g;g=g->parent){if(!AvlBalanced(*g)) //一旦发现g失衡,通过调整恢复平衡g = FromParentTo(*g)=rotateAt(tallerChild(tallerChild(g)));updateHeight(g); //更新其高度}//可能做Ω(logn)次调整;无论是否做调整,全树高度均可能下降return true; //删除成功
}

3+4重构:算法
设g(x)为最低的失衡节点,考察祖孙三代g ~ p ~ v,按中序遍历次序,将其命名为a<b<c,它们共拥有互不相交的四棵(可能为空的)子树,按中序遍历次序,将其命名为T0<T1<T2<T3。
实现

template <typename T> BinNodePosi(T) BST<T>::connect34(BinNodePosi(T) a, BinNodePosi(T) b, BinNodePosi(T) c, BinNodePosi(T) T0, BinNodePosi(T) T1, BinNodePosi(T) T2, BinNodePosi(T) T3){a->lChild = T0, if (T0) T0->parent = a;a->rChild = T1, if (T1) T1->parent = a; updateHeight(a);c->lChild = T2, if (T2) T2->parent = c;c->rChild = T3, if (T3) T3->parent = c; updateHeight(c);b->lChild = a; a->parent = b;b->rChild = c; c->parent = b; updateHeight(b);return b;//该子树新的根节点
}

统一调整:实现

template <typename T> BinNodePosi(T) BST<T>::rotateAt(BinNodePosi(T) v){BinNodePosi(T) p = v->parent, g=p->parent;//父亲、祖父if( IsLChild(*p)) //zigif( IsLChild(*v)){//zig-zigp->parent = g->parent;//向上联接return connect34(v,p,g,v->lChild,v->rChild,p->rChild,g->rChild);}else{//zig-zagv->parent = g->parent;//向上联接return connect34(p,v,g,p->lChild,v->lChild,v->rChild,g->rChild);}else{/*...zag-zig & zag-zag...*/}
}

综合评价
优点:无论查找、插入或删除,最坏情况下的复杂度均为O(logn),O(n)的存储空间。
缺点:借助高度或平衡因子,为此需改造元素结构或额外封装(splay);实测复杂度与理论值有差距(插入/删除后的旋转成本不菲,删除操作后最多需旋转Ω(logn)次,若需频繁进行插入删除未免得不偿失);单次动态调整后,全树拓扑结构变化量可能高达Ω(logn)。

第八章 高级搜索树

(a)伸展树

局部性:刚被访问过的数据,极有可能很快地再次被访问,这一现象在信息处理过程中屡见不鲜,如BST,下一个将要被访问的节点,极有可能就在刚被访问过节点的附近。连续的m次查找(m>>n=|BST|),采用AVL共需 O(mlogn) 时间,利用局部性能否更快?(仿效自适应链表)
逐层伸展:节点v一旦被访问,随即转移至树根。一步一步往上爬,自下而上,逐层单旋(zig( v->parent ),zag( v->parent )),直到v最终被推送至根。
最坏情况:在分别访问1~5后,全树复原。每一周期累计Ω(n^2),分摊Ω(n)。
双层伸展:向上追溯两层,而非一层,反复考察祖孙三代 g = parent( p), p = parent(v), v,根据它们的相对位置经两次旋转使v上升两层,成为子树根。
zig-zag / zag-zig与AVL树双旋完全等效,与逐层伸展别无二致。折叠效果:一旦访问最坏节点,对应路径长度随即减半,最坏情况不致持续发生,单趟伸展操作分摊 O(logn) 时间。
特殊情况:若v只有父亲,没有祖父,此时必有parent(v) == root(T),且每轮调整这种情况最多在最后出现一次,视具体形态做单次旋转zig( r )或zag( r )。
实现:伸展树接口

template<typename T> class Splay:: public BST<T>{//由BST派生
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); //删除重写
}

实现:伸展算法

template typename<T> BinNodePosi(T) Splay(T)::splay( 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(IsLChild(*p)){/*zig-zig*/attachAsLChild(g,p->rc);attachAsLChild(p,v->rc);attachAsRChild(p,g);attachAsRChild(v,p);} else{/*zig-zag*/}else if(IsRChild(*p)){/*zag-zag*/}else{/*zag-zig*/}if(!gg) v->parent = NULL; //若无曾祖父,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抵达树根
}

实现:查找算法

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; //局部性
}

伸展树的查找操作与常规BST::search()不同,很可能会改变树的拓扑结构,不再属于静态操作。
实现:插入算法
直观方法:调用BST标准的删除算法,再将_hot伸展至根,同样地,Splay::search()查找成功之后,目标节点即为树根,可直接在树根附近完成目标节点的摘除。(splay->release->splay->join)
综合评价:
(1)无需记录节点高度或平衡因子,编程简单易行(优于AVL树),分摊复杂度O(logn)(与AVL树相当);
(2)局部性强,缓存命中率极高时(即k<<n<<m),效率甚至可以更高,自适应的O(logk),任何连续的m次查找,都可以在O(mlogk + nlogn)时间内完成;
(3)仍不能保证单次最坏情况的出现,不适用于对效率敏感的场合;
(4)复杂度的分析稍微复杂,好在有初等的方法。

(b1)B-树:动机

内存越来越小,系统存储容量的增长速度远小于问题规模的增长速度。
高速缓存 - 事实1:不同容量的存储器,访问速度差异悬殊,多数存储系统都是分级组织的,最常用的数据尽可能放在更高层、更小的存储器中,实在找不到才向更低层、更大的存储器索取。
事实2:从磁盘中读写1B,与读写1KB一样快,批量式访问以页或块为单位,使用缓冲区。

#define BUFSIZ 512 //缓冲区默认容量
int setvbuf( //定制缓冲区FILE* fp,//流char* buf, //缓冲区int _Mode, //_IO F BF | _IO L BF | _IO N BFsize_t size); //缓冲区容量
int fflush(FILE* fp);//强制清空缓冲区

(b2)B树:结构

平衡的多路搜索树,经适当合并,得到超级节点:每两代合并(4路)、每三代合并(8路)、每d代合并m=2^d路,m-1个关键码,逻辑上与BBST等价。
优点:多级存储系统中使用B-树,可针对外部查找,大大减少I/O次数(AVL树每次只读出一个关键码,得不偿失),B树可以充分利用外存对批量访问的高效支持,每下降一层,以超级节点为单位读入一组关键码。
m阶B树:m阶平衡搜索树(m≥2),外部节点的深度统一相等,所有叶节点的深度统一相等,树高h = 外部节点的深度。
内部节点各有不超过m-1个(n个)关键码,m个(n+1个)分支,内部节点分支数n+1也不能太小,具体树根:2 ≤ n + 1,其余[m/2] ≤ n + 1,故亦称作([m/2], m)-树。
BTNode

template <typename T> struct BTNode{//B树节点BTNodePosi(T) parent; //父Vector<T> key; //数值向量Vector<BTNodePosi(T)> child;//孩子向量(长度比key多1)BTNode(){parent = NULL;child.insert(0,NULL);}BTNode(T e,BTNodePosi(T) lc = NULL,BTNodePosi(T) rc = NULL){parent = NULL; //作为根节点,初始时一个关键码,两个孩子key.insert(0,e);//仅一个关键码child.insert(0,lc);child.insert(1,rc);//两个孩子if (lc) lc->parent = this;if (rc) rc->parent = this;}
}

BTree

#define BTNodePosi(T) BTNode<T>* //B-树节点位置
template <typename T> class BTree{//B树
protected:int _size; int _order; BTNodePosi(T) _root; //关键码总数、阶次、根BTNodePosi(T) _hot; //search()最后访问的非空节点位置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);//删除
};

(b3)B-树:查找

实现

template <typename T> BTNodePosi(T) BTree<T>
::search(const T & e){BTNodePosi(T) v = _root;_hot = NULL; //从根节点出发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 NULL; //失败
}

最大树高
含N个关键码的m阶B树,最大高度=?为此,内部节点应尽可能“瘦”,各层节点数依次为n0=1,n1=2,n2=2×[m/2]…nk=2×[m/2]^(k-1)
考察外部节点所在层 N+1 = nh ≥2×(m/2)^(h-1),h≤1+log[m/2][(N+1)/2]=O(log(m)(N))
N个内部节点,N+1个外部节点
N种成功可能,N+1种失败可能
相对于BBST,对m=256,树高(I/O次数)约降低至1/7。
最小树高
含N个关键码的m阶B树,最小高度=?为此,内部节点应尽可能“胖”,各层节点数一次为n0=1, n1=m, n2=m ^ 2, n3= m ^ 3,nh=m^h
考察外部节点所在层 N+1=nh≤m^h,h≥log(m)(N+1)=Ω(log(m)N)
相对于BBST,对m=256,树高(I/O次数)约降至1/8。

(b4)B树:插入

算法

template <typename T> bool BTree<T>::insert(const T & e){BTNodePosi(T) v=search(e);if (v) return false; //确认e不存在Rank r = _hot->key.search(e);//在节点_hot中插入相应的位置_hot->key.insert(r+1,e);//将新关键码插至对应的位置_hot->child.insert(r+2,NULL); //创建一个空子树指针_size++; solveOverflow(_hot);//如发生上溢,需分裂return true; //插入成功
}

中位数:中值,在向量中为秩居中的元素。
分裂:设上溢节点中的关键码依次为k0,…,km-1,取中位数s=[m/2],以关键码ks为界划分为k0,…ks-1,ks,ks+1,…,km-1,关键码ks上升一层,并分裂(split),以所得的两个节点作为左、右孩子。
再分裂:若上溢节点的父亲本已饱和,在接纳被提升的关键码后也将上溢,可能持续发生,并逐层向上传播。0(1)×h=O(h)

(b5)B树:删除

算法

template <typename T> bool BTree<T>::remove(const T & e){BTNodePosi(T) v=search(e);if (!v) return false; //确认e存在Rank r = v->key.search(e);//确定e在v中的秩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; //如有必要,须做旋转或合并
}

插入导致上溢,需分裂;删除导致下溢,需合并。
旋转:节点v下溢时,必恰好包含[m/2]-2个关键码+[m/2]-1个分支。如右分支包含[m/2]-2个关键码,左分支包含关键码≥[m/2]个,经一次旋转合并。
合并:L和R或者不存在,或者所含关键码均不足[m/2]个,注意,L和R仍必有其一,且恰含[m/2]-1个关键码。O(h)

(xa1)红黑树:动机

一致性结构:支持对历史版本的访问。
蛮力实现:每个版本独立保存,各版本入口自成一个搜索结构,单次操作O(logh+logn),累计O(h+n) 时间/空间。
挑战:能否将复杂度控制在 O(n+h*logn) 内?可以,需利用相邻版本之间的关联性。
O(1)重构:大量共享,少量更新,每个版本的新增复杂度仅为O(logn)
进一步提高:比如总体O(n+h),单版本O(1)?可以,任何一次动态操作引发的结构变化量不致超过O(1),为此就树形结构的拓扑而言,相邻版本之间的差异不能超过O(1)。

(xa2)红黑树:结构

由红、黑两类节点组成的BST,统一增设外部节点NULL,使之成为真二叉树。(1)树根:必为黑色;(2)外部节点:均为黑色;(3)其余节点:若为红,则只能有黑孩子(红之子、之父必黑);(4)外部节点到根:途中黑节点数目相等(黑深度)。
(2-4)树:红黑树:提升各红节点,使之与其(黑)父亲等高,于是每颗红黑树,都对应于一棵(2,4)树,将黑节点与其哄孩子视作超级节点,无非四种组合(黑黑、黑红、红黑、红红),分别对应于4阶B树的一类内部节点。
RedBlack

template <typename T> class RedBlack::public BST<T>{//红黑树
public: //BST::search()等其余接口可直接沿用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);//更新节点x的高度
};template <typename T> int RedBlack<T>::updateHeight(BinNodePosi(T) x){x->height = max(stature(x->lc),stature(x->rc));if (IsBlack(x)) x->height++; return x->height; //只需计黑节点
}

(xa3)红黑树:插入

现拟插入关键码e,按BST的常规算法,插入之,设x的父亲p=x->parent存在,将x染红(除非它是根),此时可能出现双红。考查:x的祖父 g = p->parent,p的兄弟(x的叔父) u = p==g->lc ? g->rc : g->lc,按u的颜色,分两种情况处理。
实现

template <typename T> BinNodePosi(T) RedBlack<T>::insert(const T & e){//确认目标节点不存在(留意对_hot的设置)BinNodePosi(T) & x = search(e);if (x) return x;
//创建红节点,以_hot为父,黑高度-1x = new BinNode<T>(e,_hot,NULL,NULL,-1);_size++;
//如有必要,做双红修正solveDoubleRed(x);
//返回插入的节点return x ? x:_hot->parent;
}//无论原树中是否存有e,返回时总有x->data == e

RR-1:u->color == B
此时x、p、g的四个孩子全为黑,且黑高度相同。
(1)参照AVL树算法,做局部3+4重构,将节点X、p、g及其四棵子树按中序组合为Ta<a<T1<b<T2<c<T3。
(2)染色:b转黑,a或c转红。
Q:从B-树角度,如何理解这一情况?
(1)调整前之所以非法,是因为在某个三叉节点中插入红关键码,使得原黑关键码不再居中;
(2)调整之后效果相当于在新的四叉节点中,三个关键码的颜色改为RBR。
RR-2:u->color==R
在B树中,等效于超级节点发生上溢。
p与u转黑,g转红,在B树中等效于节点分裂,关键码g上升一层。
双红修正:复杂度
重构、染色均属常数时间的局部操作,故只需统计其总次数,红黑树的每一次插入操作都可在O(logn)时间内完成,其中至多做O(logn)次节点染色,一次“3+4”重构。

(xa4)红黑树:删除

首先按照BST常规算法,执行 r = removeAt( x, _hot ),x由孩子r接替,若二者之一为红,删除遂告完成;若x与r均黑,摘除x并代之以r后全树黑深度不再统一,原B-树种x所属节点下溢,在新树中,考察r的父亲 p = r->parent,r的兄弟 s = r == p->lc ? p->rc : p->lc

三种情况
BB-1:s为黑,且至少有一个红孩子t
BB-2R:s为黑,且两个孩子均为黑,p为红
BB-2B:s为黑,且两个孩子均为黑,p为黑
BB-3:s为红(其孩子均为黑)

复杂度
红黑树的每一次删除操作都可在O(logn)时间内完成,其中至多做O(logn)次重染色,一次“3+4”重构,一次单旋。

清华邓俊辉数据结构学习笔记(4) - 二叉搜索树、高级搜索树相关推荐

  1. 清华邓俊辉数据结构学习笔记(3) - 二叉树、图

    第五章 二叉树 (a)树 树能够结合向量的优点(search)和列表的优点(insert.remove),构成List< List >. 有根树 树是特殊的图 T = (V, E),节点数 ...

  2. 邓俊辉数据结构学习笔记3-二叉树

    二叉树及其表示 树 有根树 从图论的角度看,树等价于连通无环图.因此与一般的图相同,树也由一组项点〈vertex)以及联接与其间的若干条边〈edge) 组成.在计算机科学中,往往还会在此基础上,再指定 ...

  3. 邓俊辉数据结构学习笔记2

    列表 typedef int Rank; //秩 #define ListNodePosi(T) ListNode<T>* //列表节点位置template<typename T&g ...

  4. 邓俊辉数据结构学习笔记1

    起泡排序算法 void bubblesort1A(int A[], int n) //起泡排序算法(版本1A):0 <= n {int cmp = 0, swp = 0;bool sorted ...

  5. 清华大学邓俊辉-数据结构MOOC笔记-树的概念及逻辑表示

    清华大学邓俊辉-数据结构MOOC笔记-树的概念及逻辑表示 有关概念: 与图论略有不同,数据结构中的树:1.需要为每一颗树指定一个特殊的顶点,作为"根"(root),对应rooted ...

  6. 邓俊辉数据结构学习心得系列——如何正确衡量一个算法的好坏

    数据结构这门课主要关注如何设计合理的数据结构和算法,来简化时间复杂度和空间复杂度. 想要科学的解决这样一个优化的问题,最核心的思想也是最基础的,就是要量化问题.这也是将数学运用在实际问题中的一个基石. ...

  7. 邓俊辉数据结构学习心得系列——数据结构中所研究的算法

    写在前面的话: 本文只是个人学习邓俊辉老师C++数据结构的整理,包含了很多个人的见解(从内容到材料的组织形式).所整理的内容不保证逻辑性和完整性,仅供参考. 算法的基本性质: 有正确的输入 有正确的输 ...

  8. 邓俊辉数据结构学习-3-栈

    栈的学习 栈的应用场合 逆序输出 输出次序与处理过程颠倒,递归深度和输出长度不易预知 不是很理解 实例:进制转换 大致思路:对于进制转换,我们一般使用的都是长除法,因此要保存每次得到的余数,但是最后算 ...

  9. 邓俊辉数据结构学习-7-BST

    二叉搜索树(Binary-Search-Tree)--BST 要求:AVL树是BBST的一个种类,继承自BST,对于AVL树,不做太多掌握要求 四种旋转,旋转是BBST自平衡的基本,变换,主要掌握旋转 ...

最新文章

  1. 指尖大冒险、跳一跳升级版html5游戏全代码
  2. Innodb Buffer Pool的三种Page和链表
  3. Python基础知识(第二天)
  4. 重磅 | 一场你从未见过的数据中心盛会
  5. python库(2)—— threading
  6. javascript中函数参数以及函数中局部变量作用域一点点理解
  7. image打开rgb16 qt_QT中显示图像数据
  8. React开发(206):react代码分割之context的api
  9. codeforces 158B-C语言解题报告
  10. flink 写kafka_flink消费kafka的offset与checkpoint
  11. 为什么我十分喜欢C,却很不喜欢C++
  12. salesforce 学习(超简介,以及传送门)
  13. python数据分析-为什么Python对大数据分析那么很重要?
  14. 提高Visio制图效率的小技巧——Visio制图总结
  15. js定义对象时属性名是否加引号问题
  16. python xlwt_Python中xlwt解析
  17. python程序员自我评价_程序员节,用一句话证明你是一个程序员
  18. Mac实用技巧(三)—— 四指新建桌面
  19. 用python学概率与统计(第十二章)拟合度检验和独立性检验
  20. pyhton第五章 字典与集合 课后习题

热门文章

  1. error怎么开机 fan_电脑开机黑屏提示CPU Fan Error怎么解决?
  2. 计算机二级系统班C语言讲义,计算机二级C语言讲义8
  3. java中带参数的try(){}语法含义
  4. Python grid布局
  5. 实施工程师面试题(答案)
  6. Linux服务器搭建相关教程链接整理
  7. Visual Studio Ultimate 2013
  8. 力挺京东618,马化腾用小程序给刘强东送了一份大礼!
  9. Spring为什么需要使用三级缓存?
  10. Synchronized-偏向锁