开个坑记录一下学习Splay的历程。
模板
板子太长就放在文末了,此处感谢各位发过Splay博客的大佬。
代码分块解释:
节点
struct node {
int val, f, ch[2], sz, cnt;
} a[N];
节点的定义与普通的二叉搜索树相同,均存储了该节点代表的权值val
和出现次数cnt
,父节点f
,子节点ch[2]
,子树大小sz
。
基础
int n, tot;
//int &root = a[0].ch[1];
void update(int x) {
a[x].sz = a[a[x].ch[0]].sz + a[a[x].ch[1]].sz + a[x].cnt;
}
int son(int x) {
return a[a[x].f].ch[1] == x;
}
inline int getroot() { return a[0].ch[1]; }
定义了更新节点子树大小的函数update
,判断是否是右儿子的函数son
,节点总数tot
,空间使用量n
,同时约定根为0号节点右儿子(此思想来自@rentenglong),并且写了一个获得根的编号的函数getroot
。
int crepoint(int v, int fa) {
n++;
a[n].val = v;
a[n].f = fa;
a[n].sz = a[n].cnt = 1;
return n;
}
void destroy(int x) {
a[x].val = a[x].ch[0] = a[x].ch[1] = a[x].sz = a[x].cnt = a[x].f = 0;
if (x == n) n--;
}
这是为一个节点申请空间和删除一个节点的代码。(更优秀的做法是使用内存池,可是谁会去写呢)
Splay操作
Splay操作即将一个点旋转到根(或其他在该节点和根中间的节点)的位置,同时尽量维持树的平衡。
Splay基于更改节点x和其父亲的操作rotate
:
void connect(int x, int f, int d) {
a[x].f = f; // 将x的父亲设为f
a[f].ch[d] = x; // 将f的d儿子设为x
}
void rotate(int x) { // 儿子变爸爸操作
int y = a[x].f; // 爸爸
int mroot = a[y].f; // 爷爷
int mrootson = son(y), yson = son(x); // 是左儿子还是右儿子
int B = a[x].ch[yson^1];
connect(B, y, yson); // 自己的儿子变为爸爸的儿子
connect(y, x, (yson^1)); // 爸爸变为自己的儿子
connect(x, mroot, mrootson); // 自己变为爷爷的儿子
update(y); update(x); // 更新信息
}
emmm,注释写的很详细了虽然有点乱伦,具体内容自己手绘一下即可,注意这个操作并未改变中序遍历。
喜闻乐见的Splay操作:
void splay(int at, int to) {
to = a[to].f;
while (a[at].f != to) {
int up = a[at].f;
if (a[up].f == to) rotate(at);
else if (son(up) == son(at)) { // 父子同向
rotate(up);
rotate(at);
} else { // 父子异向
rotate(at);
rotate(at);
}
}
}
这是双旋splay(?),理论上复杂度十分优秀,但是好像是可以被卡的较慢的(但是还是最优秀的Splay写法)。以及,无论什么操作,之后都应该添加Splay。
注:这里我尝试画图来说明Splay操作的优秀之处,可是我画了画发现Splay对于旋转的选择是个很迷的事情,有时候还会造成深度的增加,所以就不贴图啦。
对节点操作
查询一个节点是否存在
int find(int v) {
int now = getroot();
while (true) {
if (a[now].val == v) {
splay(now, getroot());
return now;
}
int next = v < a[now].val ? 0 : 1;
if (!a[now].ch[next]) return 0;
now = a[now].ch[next];
}
}
代码几乎与普通的搜索树相同,仅仅多了一个Splay操作。
插入节点
int build(int v) {
tot++;
if (tot == 1) { // mark
a[0].ch[1] = crepoint(v, 0);
} else {
int now = getroot();
while (true) {
a[now].sz++;
if (v == a[now].val) {
a[now].cnt++;
return now;
}
int next = v < a[now].val ? 0 : 1;
if (!a[now].ch[next]) {
crepoint(v, now);
a[now].ch[next] = n;
return n;
}
now = a[now].ch[next];
}
}
return 0;
}
void push(int v) {
int add = build(v);
splay(add, getroot());
}
mark处的特判请注意。
删除节点
先找到x,这时x已经是根了,再找到x的前驱(如果没有就可以直接删除x,因为x是根,没有前驱就没有左子树),旋转到根,由于前驱的在二叉树上的位置的特殊性(左儿子的最深右后代),Splay时最后一次一定是父子异向或是直接旋转,这时,x的就是根的右儿子,且x没有左子树(没有小于x且大于其前驱的数),直接删除x即可。
void pop(int v) {
int deal = find(v);
if (!deal) return;
tot--;
// 如果有多个v,修改计数器即可。
if (a[deal].cnt > 1) {
a[deal].cnt--;
a[deal].sz--;
return;
}
// 只有一个v
if (!a[deal].ch[0]) { // 没有左子树,直接删除
a[0].ch[1] = a[deal].ch[1];
a[getroot()].f = 0;
} else { // 有左子树
int left = a[deal].ch[0];
while (a[left].ch[1]) left = a[left].ch[1];
splay(left, a[deal].ch[0]); // 前驱
int right = a[deal].ch[1]; // 右子树
connect(right, left, 1); connect(left, 0, 1);
update(left);
}
destroy(deal);
}
查询一个值的排名和一个排名的值
同一般二叉树。
int rank(int v) {
int ans = 0, now = getroot();
while (true) {
if (a[now].val == v) {
ans += a[a[now].ch[0]].sz + 1;
break;
}
if (now == 0) break;
if (v < a[now].val) now = a[now].ch[0];
else {
ans += a[a[now].ch[0]].sz + a[now].cnt;
now = a[now].ch[1];
}
}
if (now) splay(now, getroot());
return ans;
}
int atrank(int x) {
if (x > tot) return -INF;
int now = getroot();
while (true) {
int min = a[now].sz - a[a[now].ch[1]].sz;
if (x > a[a[now].ch[0]].sz && x <= min) break;
if (x < min) now = a[now].ch[0];
else {
x = x - min;
now = a[now].ch[1];
}
}
splay(now, getroot());
return a[now].val;
}
查询前驱后继
同一般二叉树
int upper(int v) {
int now = getroot();
int res = INF;
while (now) {
if (a[now].val>v && a[now].val < res) res = a[now].val;
if (v < a[now].val) now = a[now].ch[0];
else now = a[now].ch[1];
}
splay(a[now].f, getroot()); // mark
return res;
}
int lower(int v) {
int now = getroot();
int res = -INF;
while (now) {
if (a[now].val < v && a[now].val > res) res = a[now].val;
if (v > a[now].val) now = a[now].ch[1];
else now = a[now].ch[0];
}
splay(a[now].f, getroot());
return res;
}
之后补play对序列操作。
Code
struct node {
int val, f, ch[2], sz, cnt;
} a[N];
int n, tot;
void update(int x) {
a[x].sz = a[a[x].ch[0]].sz + a[a[x].ch[1]].sz + a[x].cnt;
}
int son(int x) {
return a[a[x].f].ch[1] == x;
}
void connect(int x, int f, int d) {
a[x].f = f;
a[f].ch[d] = x;
}
void rotate(int x) {
int y = a[x].f;
int mroot = a[y].f;
int mrootson = son(y), yson = son(x);
int B = a[x].ch[yson^1];
connect(B, y, yson); connect(y, x, (yson^1)); connect(x, mroot, mrootson);
update(y); update(x);
}
void splay(int at, int to) {
to = a[to].f;
while (a[at].f != to) {
int up = a[at].f;
if (a[up].f == to) rotate(at);
else if (son(up) == son(at)) {
rotate(up);
rotate(at);
} else {
rotate(at);
rotate(at);
}
}
}
int crepoint(int v, int fa) {
n++;
a[n].val = v;
a[n].f = fa;
a[n].sz = a[n].cnt = 1;
return n;
}
void destroy(int x) {
a[x].val = a[x].ch[0] = a[x].ch[1] = a[x].sz = a[x].cnt = a[x].f = 0;
if (x == n) n--;
}
inline int getroot() { return a[0].ch[1]; }
int find(int v) {
int now = getroot();
while (true) {
if (a[now].val == v) {
splay(now, getroot());
return now;
}
int next = v < a[now].val ? 0 : 1;
if (!a[now].ch[next]) return 0;
now = a[now].ch[next];
}
}
int build(int v) {
tot++;
if (tot == 1) {
a[0].ch[1] = crepoint(v, 0); // mark
} else {
int now = getroot();
while (true) {
a[now].sz++;
if (v == a[now].val) {
a[now].cnt++;
return now;
}
int next = v < a[now].val ? 0 : 1;
if (!a[now].ch[next]) {
crepoint(v, now);
a[now].ch[next] = n;
return n;
}
now = a[now].ch[next];
}
}
return 0;
}
void push(int v) {
int add = build(v);
splay(add, getroot());
}
void pop(int v) {
int deal = find(v);
if (!deal) return;
tot--;
if (a[deal].cnt > 1) {
a[deal].cnt--;
a[deal].sz--;
return;
}
if (!a[deal].ch[0]) {
a[0].ch[1] = a[deal].ch[1];
a[getroot()].f = 0;
} else {
int left = a[deal].ch[0];
while (a[left].ch[1]) left = a[left].ch[1];
splay(left, a[deal].ch[0]);
int right = a[deal].ch[1];
connect(right, left, 1); connect(left, 0, 1);
update(left);
}
destroy(deal);
}
int rank(int v) {
int ans = 0, now = getroot();
while (true) {
if (a[now].val == v) {
ans += a[a[now].ch[0]].sz + 1;
break;
}
if (now == 0) break;
if (v < a[now].val) now = a[now].ch[0];
else {
ans += a[a[now].ch[0]].sz + a[now].cnt;
now = a[now].ch[1];
}
}
if (now) splay(now, getroot());
return ans;
}
int atrank(int x) {
if (x > tot) return -INF;
int now = getroot();
while (true) {
int min = a[now].sz - a[a[now].ch[1]].sz;
if (x > a[a[now].ch[0]].sz && x <= min) break;
if (x < min) now = a[now].ch[0];
else {
x = x - min;
now = a[now].ch[1];
}
}
splay(now, getroot());
return a[now].val;
}
int upper(int v) {
int now = getroot();
int res = INF;
while (now) {
if (a[now].val>v && a[now].val < res) res = a[now].val;
if (v < a[now].val) now = a[now].ch[0];
else now = a[now].ch[1];
}
splay(a[now].f, getroot()); // mark
return res;
}
int lower(int v) {
int now = getroot();
int res = -INF;
while (now) {
if (a[now].val < v && a[now].val > res) res = a[now].val;
if (v > a[now].val) now = a[now].ch[1];
else now = a[now].ch[0];
}
splay(a[now].f, getroot());
return res;
}
长度约4k(极限压缩版约2k),功能较齐全。