预备知识
二叉查找树Treap
如果你不大想看那么长的文章只想尽快入手的话
那就先往下看吧
保证对小白友好()
首先也是一种二叉查找树
满足左子树上所有节点的值都小于根节点的值
右子树上所有节点的值都大于根节点的值
上面两条性质是最重要的
就是因为这两条性质才衍生出的更稳定的各类平衡树
那么,其中之一的有什么特点呢?
,百度翻译一下可以知道
呃
我们这里换一个更恰当的词来形容它
伸展
所以中文名就叫伸展树
它是对二叉搜索树的一种改进
因为二叉搜索树各类操作的复杂度可以被一条链卡成
所以聪明的人们就想出了优化的许多办法
通过各类操作让这个链变成的深度
虽然并不能保证树是绝对平衡的
但是它的各类操作的复杂度可以保持在均摊
大大滴优化
s p l a y splay splay的基本操作
作为一种平衡树
维持平衡的主要操作就是旋转
旋转的同时保持它作为二叉查找树的性质不变的优点就是使被查询多的条目更接近树根
这样查询的时候就会提高效率
下面着重讲的旋转(准确来讲叫伸展)
的旋转方式是根据下面的因素决定的
- 节点
是父节点
的左孩子还是右孩子
- 节点
是不是根节点,如果不是根节点
- 节点
是父节点
的左孩子还是右孩子
可以依次分为下面种旋转(网上都这么叫,我也觉得挺形象)
Zig
当为整棵树的根节点时
进行操作
当是
的左孩子时,对
右旋
当是
的右孩子时,对
左旋
如果对左旋和右旋不了解的建议先去看看文章开头的超链接
(、
、
为三棵子树)
我们称这种旋转为单旋转
旋转后变成这个样子
Zig-Zig
当不是根节点,且
和
同为左孩子或右孩子时
进行操作
当和
同为左孩子时,依次将
和
右旋
当和
同为右孩子时,依次将
和
左旋
我们称这种旋转为一字形旋转
比如说下面这种情况
要依次将和
右旋(顺序一定不能错)
旋转一次之后变成这个样子
再旋转一次后变成这个样子
Zig-Zag
当不是根节点,且
和
不同为左孩子或右孩子时
进行操作。
当为左孩子,
为右孩子时,将
左旋后再右旋
当为右孩子,
为左孩子时,将
右旋后再左旋
至于为什么很明显
与上面一对比就知道了
我们称这种旋转为之字形旋转
旋转两次之后就变成了这个样子
总结
这三种旋转都是由基本的左旋和右旋组成的
实现比较简单
具体实现
下面会多次提到“记得加引用”
这太重要了!!!
别嫌烦!!!
如果不加&你做的一切都是白做
调很长时间调不出来很可能就是因为它!
还有,
个人觉得指针版的好理解
结构体初始化
struct node *null; //自己定义所谓的“空指针”,避免出现野指针
struct node {
node *ch[2]; //这里用数组是为了方便访问相反的儿子,^即可
int same, val, siz; //分别是相同节点的个数,权值,子树大小
int whetherv(int v) { //返回要寻找值为v的元素该向左走还是向右走
if (v == val) return -1;
else return v < val ? 0 : 1; //0向左走
}
int whetherk(int k) { //返回要寻找第k小元素该向左走还是向右走
if (k <= ch[0]->siz) return 0; //向左走
else if (k <= ch[0]->siz + same) return -1; //找着了
else return 1;
}
void update() { //更新子树大小
siz = same + ch[0]->siz + ch[1]->siz;
}
node(int x) : same(1), val(x), siz(1) { //用构造函数初始化
ch[0] = ch[1] = null;
}
} *root; //定义根节点指针
#define ND node* //宏定义一下方便下面引用
当然还要初始化
void init() {
null = new node(0);
root = null->ch[0] = null->ch[1] = null;
null->siz = null->same = 0;
}
这个很明显
只要明白了左旋和右旋就看得懂
手动模拟一下也可以
指针还是很好理解的
别忘加引用
void rotate(ND &now, int l) { //l=0左旋,别忘加引用
ND x = now->ch[l ^ 1];
now->ch[l ^ 1] = x->ch[l];
x->ch[l] = now;
now->update(); x->update();
now = x;
}
别忘加引用
void splay(int val, ND &f) { //在树f中寻找值为val的节点,并伸展成为k的根节点
int dir = f->whetherv(val); //下面一步要走的方向
if (dir != -1 and f->ch[dir] != null) { //如果可以走
int dir2 = f->ch[dir]->whetherv(val); //下面两步要走的方向
if (dir2 != -1 and f->ch[dir]->ch[dir2] != null) { //如果可以走
splay(val, f->ch[dir]->ch[dir2]); //递归
if (dir == dir2) rotate(f, dir2 ^ 1), rotate(f, dir ^ 1); //zig-zig
else rotate(f->ch[dir], dir2 ^ 1), rotate(f, dir ^ 1); //zig-zag
}
else rotate(f, dir ^ 1); //zig
} //否则已经找到
}
别忘加引用
void splaykth(int k, ND &f) { //在树f中寻找第k小的节点,并伸展成为f的根节点
int dir = f->whetherk(k); //下一步要走的方向
if (dir == 1) k -= f->ch[0]->siz + f->same; //调整k
if (dir == -1) return; //不能走
int dir2 = f->ch[dir]->whetherk(k); //下面两步要走的方向
int k2 = (dir2 == 1) ? k - (f->ch[dir]->ch[0]->siz + f->ch[dir]->same) : k; //调整k2
if (dir2 != -1) { //能走的话
splaykth(k2, f->ch[dir]->ch[dir2]); //递归
if (dir == dir2) rotate(f, dir2 ^ 1), rotate(f, dir ^ 1); //zig-zig
else rotate(f->ch[dir], dir2 ^ 1), rotate(f, dir ^ 1); //zig-zag
}
else rotate(f, dir ^ 1); //zig
}
写过的应该很熟悉
别忘加引用
ND split(int val, ND &f) { //把树分成小于等于val的和大于val的
if (f == null) return null;
splay(val, f);
node *x, *y;
if (f->val <= val) x = f, y = f->ch[1], f->ch[1] = null;
else x = f->ch[0], y = f, f->ch[0] = null;
f->update();
f = x; //f中的元素都小于等于val
return y; //返回的树中元素都大于val
}
别忘加引用
void merge(ND &x, ND &y) { //两棵树合并到x上,y会变成空树
if (x == null) swap(x, y);
splay(INF, x);
x->ch[1] = y;
y = null;
x->update();
}
分裂后插入元素,有点像的操作
别忘加引用
void insert(int val, ND &f = root) {
ND x = split(val, f);
if (f->val == val) f->same++;
else {ND nw = new node(val); merge(f, nw);}
merge(f, x);
}
可不要真傻乎乎地写
别忘加引用
void dele(int val, ND &f = root) {
ND x = split(val, f);
if (f->val == val and --(f->same) < 1) { //看是否有重复节点,真没了的话才删掉它
ND y = f->ch[0];
delete f;
f = y;
}
merge(f, x);
}
查询
的排名
别忘加引用
int rank(int k, ND &f = root) {
splay(k, f);
return f->ch[0]->siz + 1;
}
查询排名为
的数
别忘加引用
int kthhh(int k, ND &f = root) {
splaykth(k, f);
return f->val;
}
查询
的前驱
别忘加引用
int pre(int k, ND &f = root) {
splay(k, f);
if (f->val >= k) {
if (f->ch[0] == null) return -INF;
splay(INF, f->ch[0]); //伸展左子树的最大值
return f->ch[0]->val;
}
else return f->val;
}
查询
的后继
别忘加引用
int nxt(int k, ND &f = root) {
splay(k, f);
if (f->val <= k) {
if (f->ch[1] == null) return INF;
splay(-INF, f->ch[1]); //伸展右子树的最小值
return f->ch[1]->val;
}
else return f->val;
}
下面给板子喽
Luogu 3369 普通平衡树
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <complex>
#include <algorithm>
#include <climits>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iomanip>
#define A 1000010
#define B 2010
using namespace std;
typedef long long ll;
const int INF = 0x7fffffff;
namespace Splay {
struct node *null;
struct node {
node *ch[2];
int same, val, siz;
int whetherv(int v) {
if (v == val) return -1;
else return v < val ? 0 : 1;
}
int whetherk(int k) {
if (k <= ch[0]->siz) return 0;
else if (k <= ch[0]->siz + same) return -1;
else return 1;
}
void update() {
siz = same + ch[0]->siz + ch[1]->siz;
}
node(int x) : same(1), val(x), siz(1) {
ch[0] = ch[1] = null;
}
} *root;
#define ND node*
void init() {
null = new node(0);
root = null->ch[0] = null->ch[1] = null;
null->siz = null->same = 0;
}
void rotate(ND &now, int l) {
ND x = now->ch[l ^ 1];
now->ch[l ^ 1] = x->ch[l];
x->ch[l] = now;
now->update(); x->update();
now = x;
}
void splay(int val, ND &f) {
int dir = f->whetherv(val);
if (dir != -1 and f->ch[dir] != null) {
int dir2 = f->ch[dir]->whetherv(val);
if (dir2 != -1 and f->ch[dir]->ch[dir2] != null) {
splay(val, f->ch[dir]->ch[dir2]);
if (dir == dir2) rotate(f, dir2 ^ 1), rotate(f, dir ^ 1);
else rotate(f->ch[dir], dir2 ^ 1), rotate(f, dir ^ 1);
}
else rotate(f, dir ^ 1);
}
}
void splaykth(int k, ND &f) {
int dir = f->whetherk(k);
if (dir == 1) k -= f->ch[0]->siz + f->same;
if (dir == -1) return;
int dir2 = f->ch[dir]->whetherk(k);
int k2 = (dir2 == 1) ? k - (f->ch[dir]->ch[0]->siz + f->ch[dir]->same) : k;
if (dir2 != -1) {
splaykth(k2, f->ch[dir]->ch[dir2]);
if (dir == dir2) rotate(f, dir2 ^ 1), rotate(f, dir ^ 1);
else rotate(f->ch[dir], dir2 ^ 1), rotate(f, dir ^ 1);
}
else rotate(f, dir ^ 1);
}
ND split(int val, ND &f) {
if (f == null) return null;
splay(val, f);
node *x, *y;
if (f->val <= val) x = f, y = f->ch[1], f->ch[1] = null;
else x = f->ch[0], y = f, f->ch[0] = null;
f->update();
f = x;
return y;
}
void merge(ND &x, ND &y) {
if (x == null) swap(x, y);
splay(INF, x);
x->ch[1] = y;
y = null;
x->update();
}
void insert(int val, ND &f = root) {
ND x = split(val, f);
if (f->val == val) f->same++;
else {ND nw = new node(val); merge(f, nw);}
merge(f, x);
}
void dele(int val, ND &f = root) {
ND x = split(val, f);
if (f->val == val and --(f->same) < 1) {
ND y = f->ch[0];
delete f;
f = y;
}
merge(f, x);
}
int rank(int k, ND &f = root) {
splay(k, f);
return f->ch[0]->siz + 1;
}
int kth(int k, ND &f = root) {
splaykth(k, f);
return f->val;
}
int pre(int k, ND &f = root) {
splay(k, f);
if (f->val >= k) {
if (f->ch[0] == null) return -INF;
splay(INF, f->ch[0]);
return f->ch[0]->val;
}
else return f->val;
}
int nxt(int k, ND &f = root) {
splay(k, f);
if (f->val <= k) {
if (f->ch[1] == null) return INF;
splay(-INF, f->ch[1]);
return f->ch[1]->val;
}
else return f->val;
}
}
int n, opt, x, y;
int main() {
Splay::init();
cin >> n;
while (n--) {
cin >> opt >> x;
switch(opt) {
case 1 : Splay::insert(x); break;
case 2 : Splay::dele(x); break;
case 3 : cout << Splay::rank(x) << endl; break;
case 4 : cout << Splay::kth(x) << endl; break;
case 5 : cout << Splay::pre(x) << endl; break;
case 6 : cout << Splay::nxt(x) << endl; break;
default : break;
}
}
return 0;
}