Splay伸展树
伸展树(Splay Tree)是一种二叉排序树,能在最坏平摊LogN时间内完成插入、查找和删除操作。是一种自调整形式的二叉树,它会沿着某个节点到跟的路径,通过一系列旋转把这个节点搬到根节点去。
Splay树的基本操作:
1、旋转和伸展
2、查找
3、插入、删除
4、最大最小值
5、前驱后继
6、合并、分离
一般Splay的节点数据:
struct node{
int val,size; //节点值,节点大小
node * ch[2],*fa; //左右节点,父节点
}*root=NULL;
Splay旋转:
void Rotate(Node *&x,int c){//c==0 左旋 c==1右旋
Node *y=x->fa;
//PushDown(y);
//PushDown(x);
//PushDown(x->ch[c]);
y->ch[!c] = x->ch[c];
if(x->ch[c] != NULL) x->ch[c]->fa = y;
x->fa = y->fa;
if(y->fa != NULL)
if(y->fa->ch[0] == y) y->fa->ch[0] = x;
else y->fa->ch[1] = x;
x->ch[c] = y; y->fa = x;
//UpDate(y);
if(y == root) root=x;
}
注意:前方高能
伸展操作:
情况一:节点x的父节点y是根节点。这时,如果x是p的左孩子,我们进行一次Zig(右旋)操作;如果x 是p 的右孩子,则我们进行一次Zag(左旋)操作。经过旋转,x成为二叉查找树的根节点,调整结束。即:如果当前结点父结点即为根结点,那么我们只需要进行一次简单旋转即可完成任务,我们称这种旋转为单旋转。如图1所示:
情况二:(一般称作“一字型旋转”)节点x 的父节点p 不是根节点,p 的父节点为g,且x 与p 同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次Zig-Zig操作或者Zag-Zag操作。即:设当前结点为x , x 的父结点为p ,p 的父结点为g ,如果p 和x 同为其父亲的左孩子或右孩子,那么我们先旋转p ,再旋转x 。如图2所示:
情况三:(一般称作“之字型旋转”)节点x的父节点p不是根节点,p的父节点为g,x与p中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次Zig-Zag操作或者Zag-Zig 操作。即:这时我们连续旋转两次X 。如图3所示:
说明:这种情况先将x转到它的父亲的位置上,再与g旋转
SPLAY伸展操作代码:
// node 为结点类型,其中ch[0]表示左结点指针,ch[1]表示右结点指针
// pre 表示指向父亲的指针
void Rotate(Node *&x,int c){//c==0 左旋 c==1右旋
Node *y=x->fa;
//PushDown(y);
//PushDown(x);
//PushDown(x->ch[c]);
y->ch[!c] = x->ch[c];
if(x->ch[c] != NULL) x->ch[c]->fa = y;
x->fa = y->fa;
if(y->fa != NULL)
if(y->fa->ch[0] == y) y->fa->ch[0] = x;
else y->fa->ch[1] = x;
x->ch[c] = y; y->fa = x;
//UpDate(y);
if(y == root) root=x;
}
void Splay(Node *&cur,Node *&f){//将cur转到f的位置
for(PushDown(cur); cur != f ;){
if(cur->fa == f)
if(f->ch[0] == cur) Rotate(cur,1);
else Rotate(cur,0);
else {
Node *y = cur->fa,*z = y->fa;
if(z->ch[0] == y)
if(y->ch[0] == cur)
Rotate(y,1),Rotate(cur,1);//yi
else Rotate(cur,0),Rotate(cur,1);//zhi
else if(y->ch[1] == cur)
Rotate(y,0),Rotate(cur,0);//yi
else Rotate(cur,1),Rotate(cur,0);//zhi
if(z==f) break;
}
//UpDate(cur);
}
//UpDate(cur);
}
如果说要把某个节点转到根的位置,你可以假设还有一个须根,当前树的根永远吊在须根上!!(个人心得、仅供参考)
Splay树的递归建立
当建树节点的值是有序的时候,可以利用递归方式建树,类似于线段树的建树方式,效率还是比较高的。
void build(node *fa,node *&cur,int l,int r){
if(l>r)return;
int mid=(l+r)>>1;
cur=newnode(sz[mid]);
cur->pre=fa;
build(cur,cur->ch[0],l,mid-1);
build(cur,cur->ch[1],mid+1,r);
update(cur);
}
Splay的节点查找
首先按照二叉排序树的性质(左子树都比它小,右子树都比它大),然后将该节点伸展到根节点。。
//查找键值为X的节点的位置并将其旋转到根节点
node *bst_search(node *cur,int x){
if(!cur) return NULL;
if(cur->sh == x) return cur;
else if(x > cur->sh)
return bst_search(cur->ch[1],x);
else if(x < cur->sh)
return bst_search(cur->ch[0],x);
}
//bst_search 返回的是键值为x的元素的位置
node *search(node *cur,int x){
node *p=bst_search(cur,x);
//此时p即为元素x的位置
splay(p,cur->pre);
return p;
}
找到处在中序遍历第k 个结点,并将其旋转到结点f 的下面:
void Select(int k, node *&f){
int tmp;
node *t;
for(t=root;;) { // 从根结点开始
Push_Down(t); // 由于要访问t 的子结点,将标记下传
tmp = t->ch[0]->size;//得到t 左子树的大小
if (k == tmp+1) break;//得出t 即为查找结点,退出循环
if (k <= tmp) // 第k 个结点在t 左边,向左走
t = t->ch[0];
else // 否则在右边,而且在右子树中,这个结点不再是第k 个
k -= tmp + 1, t = t->ch[1];
}
Push_Down(t);
splay(t,f); // 执行旋转
}
Splay节点插入:
按照二叉排序树插入,然后将其旋转到根节点下
结构体里写上这个函数:
node *newnode(int x){
node *cur=new node;
cur->val = x;
cur->ch[0] = cur->ch[1] = cur->pre = NULL;
}
然后是插入函数:
//插入值为x的一个新节点
void insert(node * cur,int x){
if(root==NULL){//cur不可改变,root要特判
root=newnode(x);
return ;
}
node *p;
while(cur!=NULL){
p=cur;
if(cur->val>x) cur = cur->ch[0];
else cur = cur->ch[1];
}
//在 while 过程当中 p始终保持是cur的父节点
cur=newnode(x);
if(cur->val < p->val) p->ch[0]=cur; //注意判读条件,不可用cur==p->ch[0]
else p->ch[1]=cur;
cur->pre = p;
splay(cur,NULL);
}
Splay的合并:
将俩棵伸展树合并必须保证第二课伸展树的值都大于第一棵伸展树,只需要将第一棵伸展树的最大节点转到根的位置上,这时候该节点的有孩子为空(因为:该节点是最大的节点,如果他还有右孩子的话根据二叉排序树的定义他的右孩子比他大),直接将第二棵伸展树挂到第一棵伸展树的最大节点的右孩子上。。
node *merge(node *x,node *y){
if(!x) return y;
if(!y) return x;
while(x->ch[1]) x = x->ch[1];
splay(x,NULL);
x->ch[1] = y;
y->pre = x;
return x;
}
Splay分离:
方法:查找给定值得节点并伸展到根,返回它的左右孩子
//Splay 分离
void split(node *cur,int x,node *&x,node *&y){
node *t=search(cur,x);
x=cur->ch[0];x->pre=NULL;
y=cur->ch[1];y->pre=NULL;
}
Splay节点删除:
//值为x的节点删除
node *del(node *cur,int x){
node *t=search(cur,x);
return merge(cur->ch[0],cur->ch[1]);
}
上面的内容看看还行,代码就算了,代码是我粘的我们吕老师的,指针写的是又臭又长又丑,代码还的看我的。。