范浩强平衡树(FHQ_Treap)介绍

F H Q   T r e a p FHQ\ Treap FHQ Treap又称无旋 T r e a p Treap Treap ,由范浩强发明。他抛弃了旋转操作,使用分裂合并两个操作来维护树的平衡。

T r e a p = t r e e + h e a p Treap = tree + heap Treap=tree+heap ,但竞赛中 s p l a y splay splay 更常用。

存储

平衡树上的每个节点放 两个值:树的权值 val 和堆的随机值 key ,对于 val 值,维护查找树的性质,对于key 值,维护堆的性质。这样,即使顺序插入一个有序序列,也不会退化为一条链。堆的随机值等价于随机打乱了有序序列插入的顺序。

struct Node {
    int l, r;  //左右儿子
    int key;   //树的权值
    int val;   //堆的随机值
    int size;  //子树大小
}tr[N];
支持操作

1、创建新节点 newnode

2、更新树的大小 pushup

3、分裂 split

O ( l o g n ) O(logn) O(logn)

  • 按值分裂:根据给的值 v v v 把―棵树分裂成两棵树,—棵树的值 v a l ≤ v val ≤ v valv,另一棵树的值 v a l > v val > v val>v

  • 如果当前节点 pval <= v ,说明 p 以及其左子树都属于分裂后的左 T r e a p Treap Treapp 的右子树也可能部分 $≤ $ v,因此需要继续递归分裂右子树,把 ≤ \le v 的那部分作为 p 的右子树。把 x 指向左 T r e a p Treap Treap 的根。

  • 如果当前节点 pval > > > v ,说明 p 以及其右子树都属于分裂后的右 T r e a p Treap Treapp 的左子树也可能部分 > > > v ,因此需要继续递归分裂左子树,把 > > > v 的那部分作为 p 的左子树。把 y 指向右 T r e a p Treap Treap 的根。

4、合并 merge

O ( l o g n ) O(logn) O(logn)

  • 合并函数两个参数:左 T r e a p Treap Treap 的根指针 x 、右 T r e a p Treap Treap 的根指针 y 。必须满足 x 中所有结点的 val 值小于等于 y 中所有结点的 val 值。
  • 因为两个 T r e a p Treap Treap 已经有序,所以在合并的时候只需要考虑把哪个树放在上面,把哪个树放在下面,也就是需要判断将哪个一个树作为子树。根据堆的性质,我们把 key 值小的放在上面。

5、插入 insert

6、删除 del

7、查询排名为 k 的节点 get_k

8、前驱和后继

9、查询数值 v 的排名

代码
/*
1、构造新节点。
v:创建的新节点的权值
返回值:创建节点的编号
*/
int newnode(int v) {
    tr[++ idx].val = v;
    tr[idx].key = rand();
    tr[idx].size = 1;
    return idx;
}


/*
2、向上更新父亲节点的树的大小。
*/
void pushup(int u) {
    tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + 1;
}


/*
3、分裂以u为树根的树,x指向左根,y指向右根。
u:当前节点
v:传入的数值
x:指向左Treap的根
y:指向右Treap的根
*/
void split(int u, int v, int &x, int &y) {
    if (!u) {
        x = 0, y = 0;
        return;
    }
    if (tr[u].val <= v) {
        x = u;
        split(tr[u].r, v, tr[u].r, y);
    } else {
        y = u;
        split(tr[u].l, v, x, tr[u].l);
    }
    pushup(u);
}


/*
4、将以x为左根,y为右根的两棵树合并成一棵。
x 左Treap的根
y 右Treap的根
返回值:合并之后的新树根
*/
int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].key <= tr[y].key) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    } else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}


/*
5、插入一个权值为 v 的点。
*/
void insert(int v) {
    int x, y, z;
    split(root, v, x, y);
    z = newnode(v);
    root = merge(merge(x, z), y);
}


/*
6、删除权值为 v 的点。
*/
void del(int v) {
    int x, y, z;
    split(root, v, x, z);
    split(x, v - 1, x, y);
    y = merge(tr[y].l, tr[y].r);
    root = merge(merge(x, y), z);
}



/**
7、返回第 k 个节点。
p 当前节点编号
k 返回第k个节点
返回值:第k个节点的编号。
*/
int get_k(int p, int k) {
    if (k <= tr[tr[p].l].size)
        return get_k(tr[p].l, k);
    if (k == tr[tr[p].l].size + 1)
        return p;
    return get_k(tr[p].r, k - tr[tr[p].l].size + 1);
}


/*
9、找到 v 的前驱的值。
*/
void get_pre(int v) {
    int x, y;
    split(root, v - 1, x, y);
    int p = get_k(x, tr[x].size());
    printf("%d\n", tr[p].val);
    root = merge(x, y);
}


/*
9、找到 v 的后继的值。
*/
void get_suc(int v) {
    int x, y;
    split(root, v, x, y);
    int p = get_k(y, 1);
    printf("%d\n", tr[p].val);
    root = merge(x, y);
}


/*
获取权值为 v 的点在树中的排名。
*/
void get_rank(int v) {
    int x, y;
    split(root, v - 1, x, y);
    printf("%d\n", tr[x].size + 1);
    root = merge(x. y);
}


/*
获取排名为 k 的点的权值。
*/
void get_val(int k) {
    int p = get_k(root, k);
    printf("%d\n", tr[p].val);
}

参考资料:
董晓算法

你可能感兴趣的:(模板,算法,数据结构)