Lec1 AVL,Splay
Lec1: AVL+Splay
AVL
定义
\(h(x)\): 以\(x\)为根的子树的最大深度 - 空树\(h=-1\) 单个点\(h=0\)
平衡因子 \(BF(x)=h(lson(x))-h(rson(x))\) 一棵满足条件的AVL树 \(\forall x, BF(x) \in \{-1,0,1 \}\)
复杂度分析
设最大深度为k的树的最少节点数为\(h_k\),则\(h_{k+1}=h_{k}+h_{k-1}+1\) 类似斐波那契数列可以得到\(h_n=O(c^n)\)
也就是说树高是\(O(\log n)\)的
操作
旋转
旋转是AVL和splay的基础,它能在不改变一棵合法 BST 中序遍历结果的情况下改变局部节点的深度。
以根节点为视角,旋转分为左旋和右旋:(图片来自OI-wiki)
以儿子节点为视角,就可以不用区分左旋和右旋。实际上旋转是把孩子和父节点交换但保持BST性质的操作,左儿子交换上去叫右旋,右儿子交换上去叫左旋。
这样我们可以统一写出旋转操作的代码:
int chk(int x){return rson(fa(x))==x;}
void rotate(int x){
//把x换到父亲的位置
int y=fa(x),z=fa(y),k=chk(x),w=tree[x].ch[k^1];
tree[y].ch[k]=w;
fa(w)=y;
if(z) tree[z].ch[chk(y)]=x;
fa(x)=z;
tree[x].ch[k^1]=y;
fa(y)=x;
pushup(y);
pushup(x);
}
插入
插入一个点后,可能会破坏关系。称插入的点为Trouble maker,递归向上找到的第一个不满足关系的点为trouble finder
LL Rotation: 把B换到A的位置
trouble finder是A, 插入点在B的左儿子子树中(也有可能是那个点)
LR Rotation:CB互换,CA互换
void maintain(int x){
if(abs(h(lson(x))-h(rson(x)))>=2){//如果>=2
if(h(lson(x))>h(rson(x))){
if(h(lson(lson(x)))>h(rson(lson(x)))){
rotate(lson(x));//LL single rotation
}else{
rotate(rson(lson(x)));//LR double rotation
rotate(lson(x));
}
}
//... 右节点的情况对称
插入: 先按照BST插入节点,接着向上,找到第一个不满足AVL操作的点,消除这个点即可。因此AVL插入的rotate次数≤2
Splay
定义
操作
核心思想:操作某个点时(查询、插入、删除),就通过旋转将目标点转移到根部
splay
两种情况:
void splay(int x,int goal=0) { //把x转到goal的子节点,goal默认=0的时候转到根
while(tree[x].fa!=goal) {
int y=tree[x].fa;
int z=tree[y].fa;
if(z!=goal) {
if(chk(x)==chk(y)) rotate(y);//zig-zig
else rotate(x);//zig-zag
}
rotate(x);//如果z就是根,那么只需要zig一次
}
if(!goal) root=x;
}
}
势能分析
让消耗大的那一步操作势能减小
multipop
有一个栈,两种操作 - Push 进去1个元素,消耗1时间 - 每次可以选择 Pop k个元素(只要栈里有),消耗k时间。 现在一共有n 个元素,问经过一系列合法操作之后总共消耗多少时间。
让消耗大的操作势能减少, 那么pop k的消耗比较大, 势能应该变小. 所以定义\(\Phi(S_k)\)代表此时栈中的元素个数。 最后栈中元素可能有0~n个,故\(\Phi(S_n) \geq \Phi(S_0)\)满足势能分析的条件
- push \(a_i=1, \Phi_i-\Phi_{i-1}=1\)
- pop \(a_i=k, \Phi_i-\Phi_{i-1}=-k\)
所以总时间\(\sum( a_i+\Phi_i-\Phi_{i-1}) \leq 2n\)
std::vector
如果有空位,就插入。否则新建一个大小为原来2倍的表,把原有元素复制进去. 假设最后表是满的
复制的操作对应势能减小。可以设势能为表中的空位个数的相反数 开始时为-1,最后为0,满足放缩条件。 - 情况1 \(a_i=1, \Phi_i-\Phi_{i-1}=1\) - 情况2 \(a_i=k, \Phi_i-\Phi_{i-1}=(-k)-0=-k\)
总时间\(O(n)\), 平均下来每次是\(O(1)\)
Splay
定义势函数 $$\Phi(T)=\sum_{i \in T} \log Size(i)
$$ \(Size(i)\)为i子树的大小. 为了方便,定义\(rank(i)\)为子树大小取对数