【部落格】無旋式Treap

zysssss發表於2024-03-19

無旋式Treap

前言

無旋Treap,又稱fhq_treap,是範浩強大佬發明的一種資料結構

它也是一棵平衡二叉搜尋樹

普通的Treap透過旋轉來保持平衡

而fhq treap不需要旋轉就可以維持平衡

它可以支援一切Treap和Splay等平衡樹的操作,支援可持久化

對於初學者來說,它比Splay好學,比Treap好用,實在不失為一個價效比極高的資料結構.

大家對它評價好高啊

要好(四聲) 好(三聲) 學

(梅姐又出現了)


Treap

Treap就是由Tree和Heap(樹和堆)兩種資料結構組合而成的資料結構。

Treap與BST不同的是

它的每個結點除了權值(val,key)以外,還維護了一個隨機優先順序pri[x]

在Treap中 權值 維護題目要求的資訊 滿足BST性質

在Treap中 優先順序滿足小根堆的性質(夢迴左偏樹)

例如

image

因為優先順序是隨機生成的,所以它是期望平衡

有旋式Treap和無旋式Treap建出的樹都是上面那樣的

只不過維護平衡的方式不同

有旋式Treap是如何旋轉的這裡不講(因為我沒學,好像沒人用)

下面來學無旋式Treap的操作

無旋式Treap

總的來說 它是靠合併開裂維持平衡的

合併

將兩棵有序(x中元素的權值都小於y中元素的權值)的treap合併成一棵

詳見程式碼

int Merge(int x,int y)
{
    if(!x|| !y) return x+y; //一棵樹為空 返回另一棵樹
    if(tree[x].pri<tree[y].pri)//若x的優先順序小 x作根
    {
        tree[x].r=Merge(tree[x].r,y);//由於x中權值比y小,所以合併右子樹與y
        pushup(x);
        return x;
    }
    else//若y的優先順序小 y作根
    {
        tree[y].l=Merge(x,tree[y].l);//由於x中權值比y小,所以合併左子樹與x
        push_up(y);
        return y;
    }
}

分裂

  1. 按權值分裂 Split(now,val,x,y)

將一個Treap分裂為兩個,第一個Treap所有節點的權值小於等於給定\(val\)值,第二個Treap所有節點的權值大於等於給定\(val\)值。

詳見程式碼

void Split(int now,int val,int &x,int &y)
{
    if(!now)
        x=y=0;
    else
    {
        if(tree[now].val<=val)//now的權值小於val,說明左子樹都小 不用分了 去分右子樹
        {
            x=now;
            Split(tree[now].r,val,tree[now].r,y);
        }
        else//now的權值大於val 說明右子樹都大 不用分了 去分左子樹
        {
            y=now;
            Split(tree[now].l,val,x,tree[now].l);
        }
        pushup(now);
    }
    return ;
}
  1. 按大小分裂 Split(now,size,x,y)

將一個Treap分裂為兩個,x的大小為\(size\),儲存著\(now\)樹中前\(size\)小的元素

詳見程式碼

void Split(int now,int size,int &x,int &y)
{
    if(!now)
        x=y=0;
    else
    {
        pushdown(now);
        if(tree[tree[now].l].size<size)//左子樹大小不夠size 去分右子樹
        {
            x=now;
            Split(tree[now].r,size-tree[tree[now].l].size-1,tree[now].r,y);
        }
        else//否則分左子樹
        {
            y=now;
            Split(tree[now].l,size,x,tree[now].l);
        }
        update(now);
    }
    return ;
}

插入

Insert(val) 在treap中插入一個值為val的元素

新建一個節點,然後根據權值拆成左右子樹,然後Merge(Merge(左,新節點),右)即可


建樹

當我發出fhq treap如何建樹這個問題的時候

@ 聰明的xixi告訴我一個個插入

這確實是一種方法

還有可以利用笛卡爾樹的\(O(n)\)建樹的方法

演算法雜記 | 笛卡爾樹 - 知乎 (zhihu.com)

這裡有


刪除

根據權值拆成左右子樹和要拆除的節點,然後直接Merge(左,右)即可

區間操作

一般來講是透過Split剖出需要操作的區間代表的子樹,然後在根節點打標記,然後合併即可.

合併無序的兩棵樹

int AllMerge(int x,int y)
{
    if(!x || !y) return x+y;
    if(tree[x].pri>tree[y].pri)
        swap(x,y);
    int a,b;
    split(y,tree[x].val,a,b);
    lc(x)=AllMerge(lc(x),a);
    rc(x)=AllMerge(rc(x),b);
    pushup(x);
}

fhq treap的基本操作就到此為止了

合併開裂是關鍵

撒花


附例題

【模板】普通平衡樹 - DL24JPOJ

P3391 【模板】文藝平衡樹


引用來源

資料結構-Treap(樹堆) 詳解-CSDN部落格

演算法|學習筆記:史上最簡單的平衡樹——無旋Treap - 知乎 (zhihu.com)

相關文章