伸展树splay
splay是众多平衡二叉搜索树中的一种,其保持平衡的途径也是旋转,最大的区别在于查询操作会引起树结构发生变化,各种操作的均摊复杂度为O(logn)。此外,splay与其他平衡树相比,功能更为强大,支持区间操作,可以进行快速分裂与合并。
splay的基础是BST,通过旋转定义了splay操作,它支持将指定的节点旋转至树根。各种常规操作都建立在splay操作之上。
旋转操作
由于节点含有父指针,在旋转时要多维护3个节点的父指针,以及顶端结点的孩子指针。
void RotateL(Node *x) {
Node *y = x->R; y->P = x->P;
x->R = y->L; if (x->R) x->R->P = x;
y->L = x; y->L->P = y;
Node *p = y->P;
if (p) {
if (x == p->L)
p->L = y;
else
p->R = y;
}
}
void RotateR(Node *x) {
Node *y = x->L; y->P = x->P;
x->L = y->R; if (x->L) x->L->P = x;
y->R = x; y->R->P = y;
Node *p = y->P;
if (p) {
if (x == p->L)
p->L = y;
else
p->R = y;
}
}
splay操作
根据不同的形态,总共有6种情况需要处理,如果合并镜像操作,有3种情况。
以下接口将节点x旋转为y的子节点。特别的,当y为空指针时,表示将x旋至根。
void Splay(Node *x, Node *y) {
if (!x) return;
while (x->P != y) {
Node *p = x->P;
if (p->P == y) {
x == p->L ? RotateR(p) : RotateL(p);
} else {
Node *g = p->P;
if (x == p->L && p == g->L)
RotateR(g), RotateR(p);
else if (x == p->R && p == g->R)
RotateL(g), RotateL(p);
else if (x == p->L && p == g->R)
RotateR(p), RotateL(g);
else
RotateL(p), RotateR(g);
}
}
}
查找节点
Node* SplayFind(Node *&x, int key) {
Node *t = Find(x, key);
if (!t) return t;
Splay(t, nullptr);
return x = t;
}
插入节点
Node* SplayInsert(Node *&x, int key) {
Node *t = Insert(x, nullptr, key);
Splay(t, nullptr);
return x = t;
}
删除节点
删除节点时,先将待删除节点旋至根,再调BST的接口删除。
Node* SplayDelete(Node *&x, int key) {
Node *t = SplayFind(x, key);
if (!t) return t;
return Delete(x, key);
}
分裂与合并
假设要将树分成左右两部分,左边部分最大值不超过k,右边部分最小值大于k,那么可以先找到键小于等于k的最大节点,将其旋至根,此时该节点的右子树即为右边部分,剩下的为左边部分。
假设要合并两棵树,分别记为L和R,其中L的最大值小于R中的最小值,那么先将L中的最大节点旋至根,此时根肯定没有右子节点,将R作为L树根的右子节点即可。
区间操作
假定要对区间[a,b]做某种操作,记树中键小于a的最大节点为A,键大于b的最小节点为B,将A旋至根,再将B旋至A的子节点,那么键值在区间[a,b]内的所有节点均位于B的左子树中。