常用的平衡树如下:
用的最多的就是treap和splay,后者在进阶课说

treap是一种特殊的二叉搜索树(BST),BST可以做的事情treap都可以。treap相当于一个BST+Heap,treap上的Node比BST多维护一个信息——Heap的val,即他的key既要满足BST(他key大于左节点且小于右节点)他的val也要满足Heap(this.val大于左子节点和右节点的val)。
这里的val是一个随机值,key+val的组合可以让我们这颗树变成一个唯一确定的二叉搜索树:比如我要找根节点,我直接找val最大的节点,那么又因为BST当前节点的左边的key一定小于当前节点,当前节点的右边的key一定大于当前节点,可以让我们整个递归过程是一个有序的。
val是一个随机值,加上heap这个数据结构是为了BST的递归变成一个可控的有序的过程。并且保证这是一颗二叉树的结构防止退化成一条链,保证了时间复杂度为logn

查找插入删除操作其实就是模拟树上二分,每次去掉当前的一半,因此是log2N的时间复杂度
AcWing 253. 普通平衡树
#include<iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10, INF = 1e8;
int n;
struct Node{
int l, r;
int key, val;
int cnt, size;
}tr[N];
int idx, root;
void pushup(int p)
{
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}
int get_node(int key)
{
tr[++idx].key = key;
tr[idx].val = rand();
tr[idx].cnt = tr[idx].size = 1;
return idx;
}
void zig(int &p)//右旋
{
int q = tr[p].l;
tr[p].l = tr[q].r; tr[q].r = p; p = q;//改变了p的值,所以p需要引用
pushup(tr[p].r);
pushup(p);
}
void zag(int &p)//左旋
{
int q = tr[p].r;
tr[p].r = tr[q].l;tr[q].l = p;p = q;
pushup(tr[p].l);
pushup(p);
}
void build()
{
get_node(-INF), get_node(INF);
root = 1, tr[1].r = 2;
pushup(root);
if (tr[1].val < tr[tr[1].r].val) zag(root);
}
void insert(int &p, int key)
{
if (!p) p = get_node(key);
else if (tr[p].key == key) tr[p].cnt ++;
else if (tr[p].key > key)
{
insert(tr[p].l, key);
if (tr[tr[p].l].val > tr[p].val) zig(p);
}
else
{
insert(tr[p].r, key);
if (tr[tr[p].r].val > tr[p].val) zag(p);
}
pushup(p);
}
void remove(int &p, int key)
{
if (!p) return ;
else if (tr[p].key == key)
{
if (tr[p].cnt > 1) tr[p].cnt --;
else if (tr[p].l || tr[p].r)//如果有左右子树
{
if (!tr[p].r || tr[tr[p].r].val < tr[tr[p].l].val)//如果右子树为空或者左子树val大于右子树,但是我也不知道后面这个啥意思为啥优先删val小的
{
zig(p);//将p旋转到右子树进行删除(目的是将它旋转到叶子结点好删除这个节点)
remove(tr[p].r, key);
}
else
{
zag(p);
remove(tr[p].l, key);
}
}
else p = 0;//如果没有左右子树,本身就是叶子节点
}
else if (tr[p].key > key)
{
remove(tr[p].l, key);
}
else remove(tr[p].r, key);
pushup(p);
}
//下面就不需要引用了,直接遍历
int get_rank_by_key(int p, int key)
{
if (!p) return 0;
else if (tr[p].key == key) return tr[tr[p].l].size + 1;
else if (tr[p].key < key) return get_rank_by_key(tr[p].r, key) + tr[tr[p].l].size + tr[p].cnt;
else return get_rank_by_key(tr[p].l, key);
}
int get_key_by_rank(int p, int rank)
{
if (!p) return -INF;
else if (tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l, rank);
else if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
else return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}
int get_pre(int p, int key)//找严格小于key的最大值
{
if(!p) return -INF;//前面的肯定是-INF
else if(tr[p].key >= key) return get_pre(tr[p].l, key);
else return max(tr[p].key, get_pre(tr[p].r, key));
}
int get_next(int p, int key)//找严格大于key的最小值
{
if (!p) return INF;//后面的肯定是INF
else if (tr[p].key <= key) return get_next(tr[p].r, key);
else return min(tr[p].key, get_next(tr[p].l, key));
}
void solve()
{
build();
cin >> n;
int op, x, res;
while (n -- )
{
cin >> op >> x;
if (op == 1) insert(root, x);
else if (op == 2) remove(root, x);
else if (op == 3) cout << get_rank_by_key(root, x) - 1<< endl;//加1减1是因为初始化到了用到了2
else if (op == 4) cout << get_key_by_rank(root, x + 1) << endl;
else if (op == 5) cout << get_pre(root, x) << endl;
else cout << get_next(root, x) << endl;
}
}
int main()
{
solve();
return 0;
}

4725

被折叠的 条评论
为什么被折叠?



