Treap引入

前置知识

treap是由BST和heap组合而成的数据结构,这一点也体现在它的名字上:treap=tree+heap

BST中,每个节点的左儿子都比它小,右儿子都比它大,可以实现有序的遍历,但是可能因为数据特殊的排列方式,而退化为线性

heap中,每个父节点都是当前子树内权值最大(或最小)的点。

在BST的基础上加一个控制树深度的功能,就得到了一个简单的平衡树。

与AVL的人为控制深度不同,Treap借由heap的性质,来实现对树深度的压缩。

引理

一棵随机生成的二叉树,其期望深度是logN    (证明略)

(因为Treap的实现依赖此引理,所以在某些极端情况下时间复杂度有可能退化,但是可能性非常非常小)

 

实现(不使用指针的写法)

结构体声明

struct Node
{
    int l, r;
    int key, val;
    int cnt, size;
}tr[N];

其中,key键值是真实存在的,存储在BST当中的数值,即我们会用到的有效值;而val则是用于辅助进行堆排序的,我们生成的一个值。

那么,这个val的值应该取多少呢?根据引理,我们希望这棵树的高度控制在logN级别,那么,我们可以在每个节点,给val赋一个随机值,这样一来,通过使用堆的更新方式,我们就能使平衡树的高度维持在随机二叉树的水平,即logN级别。

新建节点

int get_node(int key)
{
    tr[ ++ idx].key = key;
    tr[idx].val = rand();
    tr[idx].cnt = tr[idx].size = 1;
    return idx;
}

这里的idx即index,用于指示节点的序号,相当于一个指针。

基础操作——pushup

void pushup(int p)
{
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}

pushup(p)的作用是通过p的两个子节点,来更新p节点的信息。

建树

void build()
{
    get_node(-INF), get_node(INF);
    root = 1, tr[1].r = 2;
    pushup(root);

    if (tr[1].val < tr[2].val) zag(root);
}

通过预先加入两个key值为正负无穷的节点,我们可以保证之后会插入的所有点都在这两点之间,避免了一些特判的麻烦。

zig,zag,右旋左旋

void zig(int &p)    // 右旋
{
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    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 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);
}

 

这里的cnt表示每个节点元素出现的次数,防御式编程,以处理输入中存在多个相同元素的情况。

删除元素

void remove(int &p, int key)
{
    if (!p) return;
    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].l].val > tr[tr[p].r].val)
            {
                zig(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);
}

众所周知,在BST中,要删除一个内部的节点比较麻烦,我们可以借助平衡树的性质,先把要删除的节点旋转成为叶节点,然后再执行删除操作。

更为简便的做法是,为要删除的节点打上懒标记lazy tag,因为我们的代码中已经每个节点已经有cnt这个成员,我们可以用cnt是否为零来表示lazy tag是否为true。

 

fhq-treap

【数据结构】FHQ Treap 详解 - ctjcalc - 博客园 (cnblogs.com)  可以去大佬的链接进一步了解Treap的变体喵

posted @ 2023-10-16 11:38  Gold_stein  阅读(40)  评论(0编辑  收藏  举报