1. 程式人生 > >Treap(旋轉) bzoj3224普通平衡樹

Treap(旋轉) bzoj3224普通平衡樹

Treap = Tree + heap,Tree是BST,即同時維護二叉查詢樹和堆的性質
Treap的定義:

int ch[maxn][2], val[maxn], siz[maxn], num[maxn], pri[maxn];//此處優先順序用了小根堆
int tot, rt;
#define ls ch[now][0]
#define rs ch[now][1]

ch陣列存的是左右孩子節點的下標
val是BST的值,siz是子樹大小,
num指值為val的數有多少個
pri是堆的值(此處選擇了小根堆)
tot是treap的結點總數(val相同的很多個數只佔一個結點)
rt是當前的樹根
巨集是拿來偷懶滴
1、一棵BST,無論如何左旋右旋,結點間的關係一定不會變。
2、BST在資料隨機的情況下所有操作的期望都是O(logN)
Treap就是利用以上兩點,把插入的資料通過旋轉,變得好像是隨機插入的一樣。
Treap的每次插入,先像普通的BST一樣找到插入的位置,然後給pri賦隨機值,再以旋轉的方式用pri在不改變BST的性質的前提下維護heap。

旋轉:
旋轉後siz會變,要更新

inline void pushup(int now)
{
    siz[now] = siz[ls] + siz[rs] + num[now];
}

void rotate(int &x,int dir)//把now旋轉到它的兒子的位置,dir為0代表右旋,1左旋
{
    int son = ch[x][dir];
    ch[x][dir] = ch[son][dir^1];
    ch[son][dir^1] = x;
    pushup(x);//旋轉後x在下面,先更新x
    pushup(x=son);
}

插入一個值為v的數:
遞迴寫起來比較舒服~
4種情況:
1、找到了一個空位
新建一個點,over
2、當前節點的val == v
當前結點的num+1,over
3、當前節點的val > v
遞迴往左子樹插入
檢查左孩子的pri是否小於當前的pri,小於則右旋(左子樹完成插入後可能不滿足堆的性質)
4、跟3反過來

一路上的siz都要++(下面多了一個數)

void Insert(int v,int &now)
{
    if(!now)//找到空位直接新開一個點
    {
        newnode(v,now);
        return ;
    }
    ++siz[now];
    if(val[now] == v)//這個數已經存在,num+1即可
    {
        ++num[now];
        return ;
    }
    else if(v < val[now])
    {
        Insert(v,ls);
        if
(pri[ls] < pri[now]) rotate(now,0);//左兒子優先順序小就把左兒子旋到上面來(右旋) } else { Insert(v,rs); if(pri[rs] < pri[now]) rotate(now,1);//右兒子優先順序小就把右兒子旋到上面來(左旋) } }

刪除一個值為v的數:
3種情況 吧
1、如果找到val == v的結點,且該結點的num > 1,num-1就行了
2、找到val == v的結點,且該結點的num == 1
(1)如果沒有左右子樹,直接賦個0刪了就行
(2)如果左右子樹中有一個為空,賦值為不為空的那個(就是直接拿孩子把它覆蓋掉)
(3)如果左右子樹都有,把孩子中pri小的那個轉上來(維護堆的性質不變),然後再遞迴刪當前結點(為什麼不是刪孩子結點?因為旋轉完,當前節點已經轉到孩子那裡去了啊)
3、val != v
遞迴刪左或右即可

一路上的siz都要-1
3的(3)siz不用-1,因為轉上去的孩子siz都是沒變的,轉下去的那個點被刪了,不用管它的siz

void dele(int v,int &now)
{
    if(!now)//防非法資料
        return ;
    if(val[now] == v)
    {
        if(num[now] > 1)
        {
            --siz[now];
            --num[now];
            return ;
        }
        else
        {
            if(!ls && !rs)
            {
                now = 0;
                return ;
            }
            else if(!ls || !rs)
            {
                now = ls + rs;
                return ;
            }
            else
            {
                int dir = pri[ls] > pri[rs];
                rotate(now,dir);
                dele(v,now);
            }
        }
    }
    else
    {
        --siz[now];
        dele(v,ch[now][v>val[now]]);
    }
}

查值為v的數的排名
比較簡單 ,直接看註釋吧

int rk(int v,int now)
{
    if(!now) return 0;//防非法資料
    if(v == val[now])
        return siz[ls] + 1;//siz[ls]是比v小的數的數量,+1就是v的排名
    else if(v < val[now])
        return rk(v,ls);
    else
        return rk(v,rs)+siz[ls]+num[now];//往右子樹找記得把左子樹大小和當前點的大小加上
}

查排名為k的數:
跟上面差不多

int kth(int k,int now)
{
    if(siz[ls] < k && siz[ls] + num[now] >= k)
        return val[now];
    else if(k <= siz[ls])
        return kth(k,ls);
    else
        return kth(k-siz[ls]-num[now],rs);
}

求前驅、後繼

int pre(int v,int now)
{
    if(!now) return -inf;//找到空的地方返回-inf,因為上一層有max,-inf不會計入答案
    if(val[now] >= v)//當前節點的值比v大,不可能成為前驅
        return pre(v,ls);//往左節點找
    return max(pre(v,rs), val[now]);
}

int suc(int v,int now)
{
    if(!now) return inf;
    if(val[now] <= v)//當前節點的值比v小,不可能成為後繼
        return suc(v,rs);//往右節點找
    return min(suc(v,ls), val[now]);
}

bzoj3224普通平衡樹:

#include<cstdio>
#include<queue>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 5;
const int inf = 1e9 + 9;
int ch[maxn][2], val[maxn], siz[maxn], num[maxn], pri[maxn];//優先順序用小根堆
int tot, rt;
#define ls ch[now][0]
#define rs ch[now][1]
queue<int> que;
inline void newnode(int v,int &x)
{
    if(!que.empty())
    {
        x = que.front();
        que.pop();
    }
    else
        x = ++tot;
    ch[x][0] = ch[x][1] = 0;
    num[x] = 1;
    val[x] = v;
    siz[x] = 1;
    pri[x] = rand();
}

inline void reuse(int x)
{
    que.push(x);
}

inline void pushup(int now)
{
    siz[now] = siz[ls] + siz[rs] + num[now];
}

void rotate(int &x,int dir)//把now旋轉到它的兒子的位置,dir為0代表左兒子
{
    int son = ch[x][dir];
    ch[x][dir] = ch[son][dir^1];
    ch[son][dir^1] = x;
    pushup(x);//旋轉後x在下面,先更新x
    //x = son;
    pushup(x=son);
}

void Insert(int v,int &now)
{
    if(!now)//找到空位直接新開一個點
    {
        newnode(v,now);
        return ;
    }
    ++siz[now];
    if(val[now] == v)//這個數已經存在,num+1即可
    {
        ++num[now];
        return ;
    }
    else if(v < val[now])
    {
        Insert(v,ls);
        if(pri[ls] < pri[now])
            rotate(now,0);//左兒子優先順序小就把左兒子旋到上面來(右旋)
    }
    else
    {
        Insert(v,rs);
        if(pri[rs] < pri[now])
            rotate(now,1);//右兒子優先順序小就把右兒子旋到上面來(左旋)
    }
}

void dele(int v,int &now)
{
    if(!now)
        return ;
    if(val[now] == v)
    {
        if(num[now] > 1)
        {
            --siz[now];
            --num[now];
            return ;
        }
        else
        {           
            if(!ls && !rs)
            {
                reuse(now);
                now = 0;
                return ;
            }
            else if(!ls || !rs)
            {
                reuse(now);
                now = ls + rs;
                return ;
            }
            else
            {
                int dir = pri[ls] > pri[rs];
                rotate(now,dir);
                dele(v,now);
            }
        }
    }
    else
    {
        --siz[now];
        dele(v,ch[now][v>val[now]]);
    }
}

int rk(int v,int now)
{
    if(!now) return 0;
    if(v == val[now])
        return siz[ls] + 1;
    else if(v < val[now])
        return rk(v,ls);
    else
        return rk(v,rs)+siz[ls]+num[now];
}

int kth(int k,int now)
{
    if(siz[ls] < k && siz[ls] + num[now] >= k)
        return val[now];
    else if(k <= siz[ls])
        return kth(k,ls);
    else
        return kth(k-siz[ls]-num[now],rs);
}

int pre(int v,int now)
{
    if(!now) return -inf;
    if(val[now] >= v)//當前節點的值比v大,不可能成為前驅
        return pre(v,ls);//往左節點找
    return max(pre(v,rs), val[now]);
}

int suc(int v,int now)
{
    if(!now) return inf;
    if(val[now] <= v)//當前節點的值比v小,不可能成為後繼
        return suc(v,rs);//往右節點找
    return min(suc(v,ls), val[now]);
}

int n,op,x;
int main()
{
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&op,&x);
        switch(op)
        {
            case 1: Insert(x,rt); break;
            case 2: dele(x,rt); break;
            case 3: printf("%d\n",rk(x,rt));break;
            case 4: printf("%d\n",kth(x,rt));break;
            case 5: printf("%d\n",pre(x,rt));break;
            default:printf("%d\n",suc(x,rt));
        }
    }
    return 0; 
}