Splay树,中文名一般叫做伸展树。和Treap树相同,作为平衡树,它也是通过左旋和右旋来调整树的结构。和Treap树不同的是,Splay树不再用一个随机的权值来进行平衡,而是用固定的调整方式来使得调整之后的树会比较平衡。
在左旋右旋的基础上,Splay树定义了3个操作:
1. Zig
直接根据x节点的位置,进行左旋或右旋。
该操作将x节点提升了一层。
2. Zig-Zig
若p不是根节点,还有父亲节点g,且p和x同为左儿子或右儿子,则进行Zig-Zig操作:
当x,p同为左儿子时,依次将p和x右旋;
当x,p同为右儿子时,依次将p和x左旋。
注意此处不是将x连续Zig两次。该操作将x节点提升了两层。
3. Zig-Zag
若p不是根节点,则p还有父亲节点g。且p和x不同为左儿子或右儿子,则进行Zig-Zag操作:
当p为左儿子,x为右儿子时,将x节点先左旋再右旋;
当p为右儿子,x为左儿子时,将x节点先右旋再左旋。
该操作将x节点提升了两层。
进一步在Zig,Zig-Zig和Zig-Zag操作上,Splay树定义了"Splay"操作。
对于x以及x的祖先y,splay(x, y),表示对x节点进行调整,使得x是y的儿子节点,需要保证y节点一定是x节点祖先。
值得一提的是,大多数情况下我们希望通过splay操作将x旋转至整棵树的根节点。此时只需令y=NULL即可实现。
(1)Splay树的插入和查询操作和普通的二叉搜索树没有什么大的区别,需要注意的是每次插入和查询结束后,需要对访问节点做一次Splay操作,将其旋转至根。
(2)同时由于Splay的特性,我们还有两个特殊的查询操作。在树中查找指定数key的前一个数和后一个数。
我们先将key旋转至根,那么key的前一个数一定是根节点左儿子的最右子孙,同时key的后一个数一定是根节点右儿子的最左子孙。
(3)删除操作:
首先我们查找到key的前一个数prev和后一个数next。将prev旋转至根,再将next旋转为prev的儿子。此时key节点一定是next的左儿子。那么直接将next的左儿子节点删去即可。
这里你可能会担心如果key是数中最小或者是最大的数怎么办?
一个简单的处理方式是手动加入一个超级大和超级小的值作为头尾。
(4)要删除[a,b],那么我就要想办法把[a,b]的数旋转到一个子树上,再将这个子树删掉就行了。
方法和删除一个数相同,我首先将a的前一个数prev和b的后一个数next找出来。
同样将prev旋转至根,再将next旋转为prev的儿子。
那么此时next的左子树一定就是所有[a,b]之间的数了!
如果a,b不在树中呢?把a,b插入树中,做完之后再删除不就好了!
输入
第1行:1个正整数n,表示操作数量,100≤n≤200,000
第2..n+1行:可能包含下面3种规则:
1个字母'I',紧接着1个数字k,表示插入一个数字k到树中,1≤k≤1,000,000,000,保证每个k都不相同
1个字母'Q',紧接着1个数字k。表示询问树中不超过k的最大数字
1个字母'D',紧接着2个数字a,b,表示删除树中在区间[a,b]的数。
输出
若干行:每行1个整数,表示针对询问的回答,保证一定有合法的解
6 I 1 I 2 I 3 Q 4 D 2 2 Q 2
3 1
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>
#define MIN_K 0
#define MAX_K 1000000001
using namespace std;
//伸展树Splay
struct node
{
node(int k)
{
father = left = right = NULL;
key = k;
}
int key;
node *father, *left, *right;
}*root;
//与平衡树中节点不同
void left_rotate(node* a)
{
node* b = a->father;
a->father = b->father;
if (b->father == NULL)
{
root = a;
}
else
{
if (b->father->left == b)
b->father->left = a;
else
b->father->right = a;
}
b->right = a->left;
if(a->left)
a->left->father = b;
a->left = b;
b->father = a;
}
void right_rotate(node* a)
{
node* b = a->father;
a->father = b->father;
if (b->father == NULL)
{
root = a;
}
else
{
if (b->father->left == b)
b->father->left = a;
else
b->father->right = a;
}
b->left = a->right;
if(a->right)
a->right->father = b;
a->right = b;
b->father = a;
}
void zig(node* a)
{
if (a->father->left == a)
right_rotate(a);
else
left_rotate(a);
}
void zig_zig(node* a)
{
if (a->father != root)
{
if (a->father->left == a && a->father->father->left == a->father)
{
right_rotate(a->father);
right_rotate(a);
}
else if (a->father->right == a && a->father->father->right == a->father)
{
left_rotate(a->father);
left_rotate(a);
}
}
}
void zig_zag(node* a)
{
if (a->father != root)
{
if (a->father->right == a && a->father->father->left == a->father)
{
left_rotate(a);
right_rotate(a);
}
else if (a->father->left == a && a->father->father->right == a->father)
{
right_rotate(a);
left_rotate(a);
}
}
}
//表示对x节点进行调整,使得x是y的孩子节点,需保证y为x的祖先
//若希望通过splay操作将x旋转至整棵树的根节点。只需令y=NULL即可
void splay(node* x, node* y)
{
if (x == NULL)
return;
while (x->father != y)
{
node* p = x->father;
if (p->father == y)
{
if(x != root)
zig(x);
}
else
{
if (x != root)
zig_zig(x);
if (x != root)//需要判断一下
zig_zag(x);
}
}
}
//需要注意的是每次插入和查询结束后,需要对访问节点做一次Splay操作,将其旋转至根
node* bst_find(node* n, int key)
{
if (key == n->key) return n;
else if (key < n->key)
{
if (n->left == NULL)
return NULL;
else
return bst_find(n->left, key);
}
else if (key > n->key)
{
if (n->right == NULL)
return NULL;
else
return bst_find(n->right, key);
}
}
void find(int key)
{
node* n = bst_find(root, key);
splay(n, NULL);
}
node* bst_insert(node* n, int key)
{
if (key < n->key)
{
if (n->left == NULL)
{
n->left = new node(key);
n->left->father = n;
return n->left;
}
else
return bst_insert(n->left, key);
}
else
{
if (n->right == NULL)
{
n->right = new node(key);
n->right->father = n;
return n->right;
}
else
return bst_insert(n->right, key);
}
}
void insert(int key)
{
if (root == NULL)
root = new node(key);
else
{
node* n = bst_insert(root, key);
splay(n, NULL);
}
}
node* findPre(int key)
{
find(key);
node* n = root->left;
while (n->right)
n = n->right;
return n;
}
node* findNext(int key)
{
find(key);
node* n = root->right;
while (n->left)
n = n->left;
return n;
}
void deleteByKey(int key)
{
node* pre = findPre(key);
node* next = findNext(key);
splay(pre, NULL);
splay(next, pre);
if(next)
next->left = NULL;
}
void deleteByAB(int a, int b)
{
if (a <= MIN_K) a = MIN_K + 1;
if (b >= MAX_K) b = MAX_K - 1;
if (bst_find(root, a) == NULL)
insert(a);
node* pre = findPre(a);
if (bst_find(root, b) == NULL)
insert(b);
node* next = findNext(b);
splay(pre, NULL);
splay(next, pre);
if (next)
next->left = NULL;
}
int ans = 0;
int Q(node* n, int key)
{
if (key == n->key)
return key;
if (key < n->key)
{
if (n->left == NULL)
return ans;
else
return Q(n->left, key);
}
else
{
if (n->right == NULL)
return n->key;
else
{
ans = n->key;
return Q(n->right, key);
}
}
}
int main()
{
int n, a, b;
char c;
insert(MIN_K);
insert(MAX_K);
cin >> n;
while (n--)
{
cin >> c >> a;
if (c == 'I')
{
insert(a);
}
else if (c == 'Q')
{
int ans = Q(root, a);
cout << (ans == 0?a:ans) << endl;
}
else if (c == 'D')
{
cin >> b;
deleteByAB(a, b);
}
}
return 0;
}
输入
第1行:1个正整数n,表示操作数量,100≤n≤200,000
第2..n+1行:可能包含下面3种规则:
1个字母'I',紧接着1个数字k,表示插入一个数字k到树中,1≤k≤1,000,000,000,保证每个k都不相同
1个字母'Q',紧接着1个数字k。表示询问树中不超过k的最大数字
1个字母'D',紧接着2个数字a,b,表示删除树中在区间[a,b]的数。
输出
若干行:每行1个整数,表示针对询问的回答,保证一定有合法的解
6 I 1 I 2 I 3 Q 4 D 2 2 Q 2
3 1