《数据结构》学习笔记-第八章 高级搜索树(ABST)

1.Splay_Tree伸展树(被访问的点移至树根)

节点v一旦被访问,随即转移至树根。

1.1 逐层伸展

自下而上,逐层单旋

效率低:
(1)若全树的拓扑结构始终呈现单链条结构,那就等价于一维列表
(2)被访问节点的深度,呈现周期性的算术级数的演变,平均为Ω(n)

1.2 双层伸展

向上追溯两层而非一层

zigzag/zagzig与AVL树双旋完全等效
在这里插入图片描述
zigzig/zagzag重点是这个!先旋转祖父节点!
在这里插入图片描述
zig/zag若没有祖父节点,此时必然有parent(v)==root(T),仔细思考可得,这种情况至多出现一次,且是在最后出现
在这里插入图片描述

1.3 算法实现

接口:

#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.删除:
在这里插入图片描述

2.B_Tree (超级节点)

首先得明确为什么要使用B_Tree,在多级存储系统中使用B_Tree,可针对外部查找,大大减少I/O次数,充分利用外存对批量访问的高效支持,每下降一层都以超级节点为单位,读入一组关键码。
在这里插入图片描述

2.1 结构

在这里插入图片描述
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); 
};

2.2 查找

在这里插入图片描述
根据实例来看可以更好地理解:
在这里插入图片描述
实现:

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;//失败 
}

在这里插入图片描述

2.3 插入

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); 
} 

2.4 删除

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; 
}

3.Red_Black_Tree(提升变换映射为(2,4)B_Tree)

由于树的结构在不断更新,且就树形结构的拓扑而言,相邻版本之间的差异(差异来自于旋转操作)不能超过O(1)(即旋转操作不能超过常数次),但是绝大多数的BBST却不能保证这一点。
而红黑树的任何一次操作引发的结构变化量不致超过常数次,它通过给各个节点赋予红或黑色,使树相对平衡,具有持久性
在这里插入图片描述
提升变换:将红节点上升与其父节点平行(所有底层节点高度变为一致)
在这里插入图片描述

3.1 接口定义

这里出现了[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)

3.2 插入(心里要有B树)

在这里插入图片描述
可以看到这里的插入结果满足红黑树的三个条件: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(类似于尾递归,可优化) 
 }
}

3.3 删除

在这里插入图片描述
这里的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为红,其余孩子均为黑
在这里插入图片描述

3.4 总结

在这里插入图片描述
在这里插入图片描述

4.kd_Tree

视频没有讲解,找到一个讲解很详细的博客~.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值