12
8
2015
0

Splay学习笔记~

Learned from %%%DeadFishYSY

伸展树与普通二叉查找树不同的是,可以自我调整,这就要依靠伸展操作Splay(x,S)。
 

extract from cxlove(泥不是爱酱,喵~~)

如果旋转的节点的父节点便是目标结点,那么一次旋转即可。但是在平衡树中这一步还要拆开。

如果父节点并不是目标结点,那就存在爷爷结点,如果爷爷结点和父节点方向一样,那么连续两次上面的右旋或者左旋即可。如果方向不一样,便是一左一右两次旋转。在伸展树中称为一字型和之字型。

 

虽然说的很复杂,但是在Splay中,代码已经被前辈们优化到很短,很精练。

void Rotate(int x,int kind){   //kind分别代表左右旋 
    int y=pre[x];      
    Push_Down(y);  
    Push_Down(x);  
    ch[y][!kind]=ch[x][kind];     
    pre[ch[x][kind]]=y;    
    if(pre[y])    
        ch[pre[y]][ch[pre[y]][1]==y]=x;    
    pre[x]=pre[y];    
    ch[x][kind]=y;    
    pre[y]=x;    
    Push_Up(y);    
}     
//将节点r旋转至goal
void Splay(int r,int goal){    
    Push_Down(r);  
    while(pre[r]!=goal){    
        if(pre[pre[r]]==goal)    
            Rotate(r,ch[pre[r]][0]==r);    
        else{    
            int y=pre[r];    
            int kind=(ch[pre[y]][0]==y);    
            if(ch[y][kind]==r){    
                Rotate(r,!kind);    
                Rotate(r,kind);    
            }    
            else{    
                Rotate(y,kind);    
                Rotate(r,kind);    
            }    
        }    
    }    
    Push_Up(r);    
    if(goal==0) root=r;    
}

这样的伸展操作对于我们的区间操作有什么优势呢。

可以想一个问题,对于一个区间[l,r],对于平衡树,SBT,如果很快定位到这个区间是很困难的,面对于线段树是可以做到的,那么如果要把区间进行删除,旋转,线段树便做不到了。

接下来看看Splay tree是怎么做的,首先伸展树本质还是个二叉查找树。做法也很简单,将第l-1个结点旋转至根(之前的Splay操作),将第r+1个结点旋转至根的右孩子,那么根据二叉查找树我们知道,在这两个结点之间,也是根的右孩子的左子树就包括节点[l,r],我们便很快定位,如果需要删除,直接便可以把子树拿走。其中Get_Kth表示找到第K个结点,记录子树的大小,便可以很快实现。

int Get_interval(int l,int r){
	Splay(Get_Kth(l-1),0);
	Splay(Get_Kth(r+1),root);
	Push_Up(ch[root][1]);
	Push_Up(root);
}

这个函数便可以很快找到区间[l,r]执行之后的根的右孩子的左子树便是所要区间,便可以进行操作。 

伸展树也可以完成线段树的一些工作,也可以设置延迟标记,向上更新,向下更新等等。具体的在后面的练习中会遇到。。。。。吧(@zyf)

Category: 伸展树 | Tags: | Read Count: 447

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com