Splay的基本操作(插入/删除,查询)
Splay的基本操作(插入/删除,查询)
概述
- 这是一棵二叉查找树
- 让频繁访问的节点尽量靠近根
- 将查询,插入等操作的点"旋转"至根
- 树的高度均摊为\(log_n\)
变量
int root, tot; // root为当前树根(与0相连), tot是最大的编号
struct Snode
{
int ch[2], fa, val, cnt, size;
/*
ch[0], ch[1]分别为左右儿子
fa是父亲节点, val是权值
cnt是这个权值的个数,size是子树(含自己)的总元素个数
*/
} T[MAXN];
rotate
例图:
graph TD;
Z --> Y; Z --> D;
Y --> *X*; Y --> C;
*X* --> A; *X* --> B;
z --> *x*; z --> d;
*x* --> a; *x* --> y;
y --> b; y --> c;
- 旋转\(X\) : 即把\(X\)放到父亲\(Y\)的位置,并且调整相关的\(X,Y,Z\)与儿子间的关系, 使之仍然满足二叉查找树的性质
其余三种\(X, Y, Z\)的关系的情况:
graph TD;
Z --> D; Z --> Y;
Y --> X; Y --> C;
X --> A; X --> B;
z --> d; z --> x;
x --> a; x --> y;
y --> b; y --> c;
graph TD;
Z --> Y; Z --> D;
Y --> C; Y --> X;
X --> A; X --> B;
z --> x; z --> d;
x --> y; x --> b;
y --> c; y --> a;
graph TD;
Z --> D; Z --> Y;
Y --> C; Y --> X;
X --> A; X --> B;
z --> d; z --> x;
x --> y; x --> b;
y --> c; y --> a;
void rotate(int x)
{
int y = T[x].fa, z = T[y].fa;
int zy = (T[z].ch[1] == y);
int yx = (T[y].ch[1] == x);
T[z].ch[zy] = x; T[x].fa = z;
T[y].ch[yx] = T[x].ch[yx ^ 1]; T[T[x].ch[yx ^ 1]].fa = y;
T[x].ch[yx ^ 1] = y; T[y].fa = x;
update(y); update(x);
}
splay
- 如果 "\(X\)和父亲\(Y\)的关系" 和 "\(Y\)和父亲\(Z\)的关系" 相等, 需要先
rotate(y)
再rotate(x)
void splay(int x, int tar) // x --> tar
{
tar = T[tar].fa; // x --> tar's father's son
while (T[x].fa != tar)
{
int y = T[x].fa, z = T[y].fa;
if (z != tar)
(T[z].ch[1] == y) ^ (T[y].ch[1] == x) ? rotate(x) : rotate(y);
rotate(x);
}
if (tar == 0) root = x;
}
插入
利用二叉查找树的性质,找到这个权值应该放的位置
给从根下来的路径上size
都+1
如果之前没这个值,那么新建一个节点
int newnode(int v, int fa)
{
int u = ++ tot;
if (fa) T[fa].ch[v > T[fa].val] = u;
T[u].fa = fa;
T[u].val = v;
T[u].ch[0] = T[u].ch[1] = 0;
T[u].cnt = T[u].size = 1;
return tot;
}
否则直接计数+1
void insert(int v)
{
int now = root, fa = 0;
if (root == 0) return (void) ( root = newnode(v, 0) );
while (1)
{
T[now].size ++;
if (T[now].val == v) return (void) ( T[now].cnt ++, splay(now, root) );
if (!T[now].ch[v > T[now].val])
{
T[now].ch[v > T[now].val] = newnode(v, now);
return (void) ( splay(T[now].ch[v > T[now].val], root) );
}
now = T[now].ch[v > T[now].val];
}
}
注意插入完后,将其splay
到根
查找权值\(v\)的节点编号
利用二叉查找树的性质
int find(int v)
{
int now = root;
if (!now) return 0;
while (1)
{
if (T[now].val == v) { splay(now, root); return now; }
if (!T[now].ch[v > T[now].val]) return 0;
now = T[now].ch[v > T[now].val];
}
}
查找完后,将其splay
到根
删除
-
首先找到权值对应的点,
splay
到根int now = find(x); if (!now) return ;
-
如果这个点有多个, 计数-1即可
if (T[now].cnt > 1) return (void)(T[now].cnt --, T[now].size --);
-
如果只剩这一个点, 清空即可
if (!T[now].ch[0] && !T[now].ch[1]) return (void)(root = 0);
-
如果有一个儿子, 将儿子变成根, 清空
if (!T[now].ch[0]) return (void)(root = T[now].ch[1], T[root].fa = 0); if (!T[now].ch[1]) return (void)(root = T[now].ch[0], T[root].fa = 0);
-
如果有两个儿子, 则将左子树中最大的变成根, 右子树变成现在的右儿子, 清空
int left = T[root].ch[0]; while (T[left].ch[1]) left = T[left].ch[1]; splay(left, root); T[left].ch[1] = T[now].ch[1]; T[T[now].ch[1]].fa = root; clear(now); update(root);
查找权值\(v\)的排名
可以将这个值旋转到根,排名即左子树大小+1
int rank(int v)
{
int now = find(v);
if (!now) return 0;
return T[T[root].ch[0]].size + 1;
}
或者二叉查找树做(查完splay
)
int rank(int v)
{
int ans = 0, now = root;
while (1)
{
if (T[now].val == v)
{
ans += T[T[now].ch[0]].size + 1;
splay(now, root);
return ans;
}
if (now == 0) return 0;
if (T[now].val > v) now = T[now].ch[0];
else if (T[now].val < v)
{
ans += T[T[now].ch[0]].size + T[now].cnt;
now = T[now].ch[1];
}
}
}
查找排名的权值
还是二叉查找树, 查完splay
int arank(int x)
{
if (x == 0) return 0;
int now = root;
while (1)
{
int used = T[now].cnt + T[T[now].ch[0]].size;
if (used >= x && x > T[T[now].ch[0]].size) break;
if (x > used) x -= used, now = T[now].ch[1];
else now = T[now].ch[0];
}
splay(now, root);
return T[now].val;
}
前驱/后继
二叉查找树做
int pre(int v)
{
int res = -INF, now = root;
while (now)
{
if (v > T[now].val) res = max(res, T[now].val), now = T[now].ch[1];
else now = T[now].ch[0];
}
return res;
}
int nex(int v)
{
int res = INF, now = root;
while (now)
{
if (v < T[now].val) res = min(res, T[now].val), now = T[now].ch[0];
else now = T[now].ch[1];
}
return res;
}
或者先插入, splay
到根, 然后找左边最大的/右边最小的, 最后删除