Treap

Posted 1625--h

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Treap相关的知识,希望对你有一定的参考价值。

Treap

普通二叉搜索树(BST)

  • 对于任一棵子树,根节点权值大于左子树所有结点的权值,小于右子树所有结点权值

1. 结点结构体&初始化函数

struct BST
    int l,r;
    int val;
a[SIZE];
int tot,root,INF = 1<<30;
int New(int val)
    a[++tot].val = val;return tot;

void Build()
    New(-INF),New(INF);//加两个无穷结点可以省很多事
    root = -1,a[1].r = 2;

2. 查询操作

  • 如果递归到当前结点为空结点,则说明找不到
  • 如果递归结点权值等于val,则返回当前结点
  • 否则与查询对象val比大小,决定去左还是去右边
int Get(int p,int val)
    if(p == 0)return 0;
    if(val == a[p].val)return p;
    return val < a[p].val ? Get(a[p].l,val) : Get(a[p].r,val);

3. 插入操作

  • 当前所指结点为空结点,则new一个新的,返回新结点编号
  • 如果已经存在权值为val的结点,则不新建
  • 当前结点权值与val比大小,决定递归左边还是右边,注意这里函数\(Insert\)函数的孩子“指针”参数为引用,可以实际修改。
void Insert(int &p,int val)
    if(!p)
        p = New(val);
        return ;
    
    if(val == a[p].val)return;
    if(val < a[p].val)Insert(a[p].l,val);
    else Insert(a[p].r,val);

4. 寻找后继结点,即权值大于val的前提下,关键码最小的结点

  • 如果存在一个权值为val的结点
    • 如果它有右子树,那么该子树最左边的结点就是val的后继结点
    • 如果没有,那么从root到该节点的路径上选一个权值符合的就是val的后继节点
  • 根据当前结点权值与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)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 p;

5. 删除结点

  • 如果当前结点为空结点,返回
  • 如果找到了
    • 如果只有一颗子树,那么把子树跟它的父亲连起来
    • 如果有两个子树,找到它的后继结点(也就是右子树的最左侧的结点),然后因为后继结点一定没有左子树,所以可以直接删除,然后将这个后继结点替换当前要删除的结点即可。
  • 如果没找到就继续递归找
void Remove(int &p,int val)
    if(p == 0)return;
    if(val == a[p].val)
        if(a[p].l == 0)p = a[p].r;
        else if(a[p].r == 0)p = a[p].l;
        else
            int nxt = a[p].r;
            while(a[nxt].l)nxt = a[nxt].l;
            Remove(a[p].r,a[nxt].val);
            a[nxt].l = a[p].l,a[nxt].r = a[p].r;
            p = nxt;
        
        return;
    
    if(val < a[p].val)Remove(a[p].l,val);
    else Remove(a[p].r,val);

Treap

由于升序或降序序列依次插入到BST中会导致二叉查找树查找效率退化,所以要在某些时刻进行适当调整。如果在new一个新结点的时候,给它随机赋值一个值,在满足BST的前提下,按照这个值去维护一个大顶堆(\(Tree+heap = Treap\))。那么期望下他的高度是相对较平衡的(感性理解,就是叶子结点的高度相差不多)

另外模板题 Luogo 3369 中每个相同权值的数有多个,加上还需要求排名等信息,所以Treap结点结构体如下

struct Treap
    int l,r;
    int val,dat;
    int cnt,size;
a[N];
int tot,root,n,INF = 0x7fffffff;
int New(int val)
    a[++tot].val = val;
    a[tot].dat = rand();//给随机权值赋值
    a[tot].cnt = a[tot].size = 1;return tot;

//更新当前结点所代表子树的size
void Update(int p)
    a[p].size = a[a[p].l].size + a[a[p].r].size + a[p].cnt;

void Build()
    New(-INF);New(INF);
    root = 1;a[1].r = 2;
    Update(1);

1. 查询数值x的排名

  • 如果当前节点权值等于x,则返回该节点左子树size+1
  • 如果当前节点权值大于x,那么返回左子树查询结果
  • 否则返回右子树查询结果+左子树size+当前结点cnt(cnt为当前结点权值的个数)
int GetRankByVal(int p,int val)
    if(p == 0)return 0;
    if(val == a[p].val)return a[a[p].l].size + 1;//排名,+1
    if(val < a[p].val)return GetRankByVal(a[p].l,val);
    return GetRankByVal(a[p].r,val) + a[a[p].l].size + a[p].cnt;

2. 根据排名查询值

  • 如果当前节点不存在,返回INF
  • 如果当前节点左子树size大于等于rank,那么答案在左子树中
  • 如果左子树size + 当前节点cnt 大于等于rank,那么答案就是当前节点的权值
  • 否则答案在右子树中,减去左子树和当前节点的影响继续递归右子树即可
int GetValByRank(int p,int rank)
    if(p == 0)return INF;
    if(a[a[p].l].size >= rank)return GetValByRank(a[p].l,rank);
    if(a[a[p].l].size + a[p].cnt >= rank)return a[p].val;
    return GetValByRank(a[p].r,rank - a[a[p].l].size - a[p].cnt);

3. 右旋

void zig(int &p)
    int q = a[p].l;
    a[p].l = a[q].r;a[q].r = p;p = q;
    Update(a[p].r),Update(p);

4. 左旋

void zag(int &p)
    int q = a[p].r;
    a[p].r = a[q].l;a[q].l=p;p = q;
    Update(a[p].l),Update(p);

5. 插入

  • 与BST大致相同,只是在把val插入到左子树或右子树时,要根据dat来维护Treap,即通过左旋或右旋调整使得堆性质成立
  • 最后要Update
void Insert(int &p,int val)
    if(p == 0)
        p = New(val);return;
    
    if(val == a[p].val)
        a[p].cnt ++;Update(p);
        return;
    
    if(val < a[p].val)
        Insert(a[p].l,val);
        if(a[p].dat < a[a[p].l].dat)zig(p);
    
    else
        Insert(a[p].r,val);
        if(a[p].dat < a[a[p].r].dat)zag(p);
    
    Update(p);

6. 获取前继节点和后继节点

int GetPre(int val)
    int ans = 1;
    int p = root;
    while(p)
        if(val == a[p].val)
            if(a[p].l)
                p = a[p].l;
                while(a[p].r) 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)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;

7. 删除结点

  • 因为Treap可以左旋或右旋,所以可以把要删除的结点进行左旋或右旋(旋转的时候要保证堆性质成立)到叶子节点进行删除
void Remove(int &p,int val)
    if(p == 0)return;
    if(val == a[p].val)
        if(a[p].cnt > 1)
            a[p].cnt --;
            Update(p);
            return;
        
        if(a[p].l || a[p].r)//不是叶子结点,向下旋转
            if(a[p].r == 0 || a[a[p].l].dat > a[a[p].r].dat)
                zig(p),Remove(a[p].r,val);
            
            else 
                zag(p);Remove(a[p].l,val);
            
            Update(p);
        
        else p = 0;//叶子节点,删除

        return;
    
    val < a[p].val ? Remove(a[p].l,val) : Remove(a[p].r,val);
    Update(p);

附Luogu3369
Luogu3369

#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
struct Treap
    int l,r;
    int val,dat;
    int cnt,size;
a[N];
int tot,root,n,INF = 0x7fffffff;
int New(int val)
    a[++tot].val = val;
    a[tot].dat = rand();
    a[tot].cnt = a[tot].size = 1;return tot;

void Update(int p)
    a[p].size = a[a[p].l].size + a[a[p].r].size + a[p].cnt;

void Build()
    New(-INF);New(INF);
    root = 1;a[1].r = 2;
    Update(1);

int GetRankByVal(int p,int val)
    if(p == 0)return 0;
    if(val == a[p].val)return a[a[p].l].size + 1;//排名,+1
    if(val < a[p].val)return GetRankByVal(a[p].l,val);
    return GetRankByVal(a[p].r,val) + a[a[p].l].size + a[p].cnt;

int GetValByRank(int p,int rank)
    if(p == 0)return INF;
    if(a[a[p].l].size >= rank)return GetValByRank(a[p].l,rank);
    if(a[a[p].l].size + a[p].cnt >= rank)return a[p].val;
    return GetValByRank(a[p].r,rank - a[a[p].l].size - a[p].cnt);

void zig(int &p)
    int q = a[p].l;
    a[p].l = a[q].r;a[q].r = p;p = q;
    Update(a[p].r),Update(p);

void zag(int &p)
    int q = a[p].r;
    a[p].r = a[q].l;a[q].l=p;p = q;
    Update(a[p].l),Update(p);

void Insert(int &p,int val)
    if(p == 0)
        p = New(val);return;
    
    if(val == a[p].val)
        a[p].cnt ++;Update(p);
        return;
    
    if(val < a[p].val)
        Insert(a[p].l,val);
        if(a[p].dat < a[a[p].l].dat)zig(p);
    
    else
        Insert(a[p].r,val);
        if(a[p].dat < a[a[p].r].dat)zag(p);
    
    Update(p);

int GetPre(int val)
    int ans = 1;
    int p = root;
    while(p)
        if(val == a[p].val)
            if(a[p].l)
                p = a[p].l;
                while(a[p].r) 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)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;

void Remove(int &p,int val)
    if(p == 0)return;
    if(val == a[p].val)
        if(a[p].cnt > 1)
            a[p].cnt --;
            Update(p);
            return;
        
        if(a[p].l || a[p].r)//不是叶子结点,向下旋转
            if(a[p].r == 0 || a[a[p].l].dat > a[a[p].r].dat)
                zig(p),Remove(a[p].r,val);
            
            else 
                zag(p);Remove(a[p].l,val);
            
            Update(p);
        
        else p = 0;//叶子节点,删除
        return;
    
    val < a[p].val ? Remove(a[p].l,val) : Remove(a[p].r,val);
    Update(p);

int main()
    Build();
    scanf("%d",&n);
    while(n--)
        int opt,x;
        scanf("%d%d",&opt,&x);
        switch(opt)
            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;

以上是关于Treap的主要内容,如果未能解决你的问题,请参考以下文章

Treap 学习笔记

2018131

线性构造treap

平衡树讲解(旋转treap,非旋转treap,splay)