跳转至

Treap

前置知识

二叉搜索树

目标

Treap基本操作,代码模板

What

A treap is a binary tree that maintains simultaneously the property of binary search tree (BST) and heap. A treap (tree + heap) is a binary tree whose nodes contain two values, a key and a priority , such that the key keeps the BST property and the priority is a random value that keeps the heap property (it doesn’t matter if it’s a max-heap or min-heap). A treap use the principle of binary trees formed from a random permutation in order to maintain a balanced binary search tree dynamically as nodes are inserted and deleted.

img

我们在维持 BST 性质的基础上,通过改变二叉搜索树的形态,使得树上每个结点的左右子树大小达到平衡,从而使整棵树的深度维持在O(logn)级别。 改变形态并保持 BST 性质的方法,叫做“旋转”。左旋转(Left Rotation)和右旋转(Right Rotation)。

img

在初始情况下,y 是 x 的左子结点,绿子树是 y 的右子树。 “右旋”操作,x 变为 y 的右子结点, y 原来的右子树(绿子树),变为 x 的左子树。

在初始情况下,x 是 y 的右子结点,绿子树是 x 的左子树。 “左旋”操作,y 变为 x 的左子结点,x 原来的左子树(绿子树),变为 y 的右子树。

// Right Rotate
void right_rotation(int &p) {
    int q = a[p].l;
    a[p].l = a[q].r, a[q].r = p;
    p = q;
}

// Left Rotate
void left_rotation(int &p) {
    int q = a[p].r;
    a[p].r = a[q].l, a[q].l = p;
    p = q;
}

Treap 的思想,就是利用“随机”,来创造平衡条件。在旋转过程中必须维持 BST 性质,所以 Treap 就把“随机”作用在堆的性质上。Treap 在插入一个新结点时,给该结点随机生成一个额外的权值。然后像二叉堆的插入过程一样,自底向上依次检查,当某个结点不满足大根堆性质时,就执行旋转操作,使该结点与其父结点的关系发生交换。

例题,【模板】普通平衡树 - 洛谷

题意:插入x,删除x,查询x的排名,查询排名x的是什么,求x的前驱,求x的后继 数据中可能有相同的数值,我们给每个结点增加一个域 cnt,记录该结点的“副本数”,初始为1。若插入已经存在的数值,就直接把“副本数”加1。在删除时,减少结点的“副本数”,当减为0时,删除该结点。这样就可以容易的处理关键码相同的问题。 题意还要查询排名,我们给每个结点增加一个域 size,记录以该结点为根的子树,所有结点的“副本数”之和。 与线段树一样,我们在插入和删除时,从下往上更新 size 信息。发生旋转操作时,也要更新 size。最后,在 BST 的基础上,通过判断左、右子树 size 的大小,选择适当的一侧递归,就可以查询排名了。Treap的形态会发生变化,我们用递归实现更新,以便于在回溯时,更新 Treap 上存储的 size 信息。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

struct node {
    int l, r;
    int val, dat; // 结点的关键码和权值
    int cnt, sz; // 副本数,子树大小
}a[N];

int tot, root, n, INF = 0x3f3f3f3f;

int New(int val) {
    a[++tot].val = val;
    a[tot].dat = rand();
    a[tot].cnt = a[tot].sz = 1;
    return tot;
}

void upd(int p) { // 更新子树大小
    a[p].sz = a[a[p].l].sz + a[a[p].r].sz + a[p].cnt;
}

void build() {
    New(-INF), New(INF);
    root = 1, a[1].r = 2; // 无穷小,无穷大
    upd(root);
}

void rightRotate(int &p) {
    int q = a[p].l;
    a[p].l = a[q].r, a[q].r = p, p = q;
    upd(a[p].r), upd(p);
}

void leftRotate(int &p) {
    int q = a[p].r;
    a[p].r = a[q].l, a[q].l = p, p = q;
    upd(a[p].l), upd(p);
}

void insert(int &p, int val) {
    if (p == 0) {
        p = New(val);
        return ;
    }

    if (val == a[p].val) {
        a[p].cnt++, upd(p);
        return ;
    }

    if (val < a[p].val) {
        insert(a[p].l, val);
        if (a[p].dat < a[a[p].l].dat) rightRotate(p); // 右旋
    }
    else {
        insert(a[p].r, val);
        if (a[p].dat < a[a[p].r].dat) leftRotate(p); // 左旋
    }
    upd(p);
}

void remove(int &p, int val) {
    if (p == 0) return ;
    if (val == a[p].val) {
        if (a[p].cnt > 1) {
            a[p].cnt--, upd(p);
            return ;
        }   

        if (a[p].l || a[p].r) { // 不是叶子结点,并且只有一个副本,删掉就要旋转操作
            if (a[p].r == 0 || a[a[p].l].dat > a[a[p].r].dat) {
                rightRotate(p), remove(a[p].r, val);
            }
            else {
                leftRotate(p), remove(a[p].l, val);
            }

            upd(p);
        }
        else p = 0; // 叶子节点,直接删除

        return ;
    }

    if (val < a[p].val) remove(a[p].l, val);
    else remove(a[p].r, val);

    upd(p);
}

int getRankByVal(int p, int val) {
    if (p == 0) return 0;
    if (val == a[p].val) return a[a[p].l].sz + 1;

    if (val < a[p].val) return getRankByVal(a[p].l, val);
    return getRankByVal(a[p].r, val) + a[a[p].l].sz + a[p].cnt; // 在右子树里
}

int getValByRank(int p, int Rank) {
    if (p == 0) return INF;
    if (a[a[p].l].sz >= Rank) return getValByRank(a[p].l, Rank); // 在左子树里
    if (a[a[p].l].sz + a[p].cnt >= Rank) return a[p].val; 
    return getValByRank(a[p].r, Rank - a[a[p].l].sz - a[p].cnt);
}

int getPre(int val) {
    int ans = 1; // 无穷小
    int p = root;
    while (p) {
        if (val == a[p].val) {
            if (a[p].l > 0) {
                p = a[p].l;
                while (a[p].r > 0) p = a[p].r; // 左子树上一直向右走
                ans = p;
            }
            break;
        }

        if (a[p].val < val && a[p].val > a[ans].val) ans = p;
        p = val < a[p].val ? a[p].l : a[p].r;
    }

    return a[ans].val;
}

int getNext(int val) {
    int ans = 2; // 无穷大
    int p = root;
    while (p) {
        if (val == a[p].val) {
            if (a[p].r > 0) {
                p = a[p].r;
                while (a[p].l > 0) p = a[p].l; // 右子树上一直向左走
                ans = p;
            }
            break;
        }

        if (a[p].val > val && a[p].val < a[ans].val) ans = p;
        p = val < a[p].val ? a[p].l : a[p].r;
    }

    return a[ans].val;
}

int main() {
    build();

    cin >> n;
    while (n--) {
        int op, x;
        scanf("%d%d", &op, &x);

        switch (op) {
            case 1 : insert(root, x); break;
            case 2 : remove(root, x); break;
            case 3 : printf("%d\n", getRankByVal(root, x) - 1); break;
            case 4 : printf("%d\n", getValByRank(root, x + 1)); break;
            case 5 : printf("%d\n", getPre(x)); break;
            case 6 : printf("%d\n", getNext(x)); break;
        }
    }

    return 0;
}

参考

《算法竞赛进阶指南》 https://en.wikipedia.org/wiki/Random_binary_tree https://medium.com/carpanese/a-