Treap树概念及应用

概念

  • Treap一词是Tree和Heap的合成词,也就是这是一颗带有堆性质的树,即树堆,Treap树是一种排序二叉树(也成二叉搜索树、二分检索树 Binary Serach Tree),简称BST,也就是满足value值大小关系是左孩子<根<右孩子,这样就满足了排序二叉树,刚才讲过,Treap树还满足堆的性质,那么它的哪个值满足堆的性质呢,并不是value,而是一个优先级rank值,这个rank值是人为添加的,一般使用一个随机数,这个rank是为了维持二叉树的平衡而设定的,总的说来,对于键值来说,Treap是一棵排序二叉树,对于优先级来说,Treap是一个堆,当然根节点的优先级是最高的

实现

结构

  • 首先考虑树的组成,需要键值value,优先级Rank,为了方便统计排名,需要一个sz记录当前树的节点数,需要一个cnt记录当前数字出现了多少次,需要两个指针分别指向左孩子和右孩子,每次操作之后都需要更新当前树上节点数,所以写一个
    Update函数,为了方便判断当前元素和根节点value的大小关系,需要写一个cmp函数,如果小于则在左子树,大于在右子树,相等返回-1
struct TreapNode{
    
    
    int value;
    int Rank;
    int cnt;
    int sz;
    TreapNode* son[2];
    int cmp(int x) const{
    
    
        if(x == value) return -1;
        return x < value ? 0 : 1;
    }
    void Update(){
    
    
        sz = 1;
        if(son[0] != NULL) sz += son[0]->sz;
        if(son[1] != NULL) sz += son[1]->sz;
    }
};
  • son[0]表示左孩子,son[1]表示右孩子

旋转操作

  • 为什么要旋转?考虑普通的排序二叉树,假设插入数字是有序的,那么二叉树就会退化为一条链,这样查询就和数组没有什么区别了,所以Treap树就制定了一个随机给的rank值,按照它的大小关系进行相应的旋转,使得二叉树保持平衡不退化为链

左旋和右旋

在这里插入图片描述

  • 上图针对于9进行了旋转,从左到右是右旋,从右到左是左旋,这里可以理解为右旋为顺时针,左旋为逆时针

右旋

  • 这个图,通俗一点讲,右旋要断三根线:待旋节点和它的父亲节点、待旋节点和左孩子、左孩子和它的右孩子,然后把左孩子的右孩子作为待旋节点的左孩子,待旋节点作为它左孩子的右孩子,再把待旋节点左孩子放在待旋节点的位置上,这里注意一点,我们要传的是待旋节点的引用,引用不改变地址,只改变值,这样我们才能让6到9的位置上而依然保持和9父亲之间的关系

左旋

  • 相对的,左旋是先向右,再向左,也就是找右孩子,再找右孩子的左孩子,让右孩子的左孩子作为待旋节点的右孩子,待旋节点作为它右孩子的左孩子,完成左旋操作

旋转总结

  • 可以发现,左旋和右旋是一种互逆的关系,右旋找左孩子,左旋找右孩子,这样可以通过1 - x的操作来实现,更快的方法是取异或,程序如下
void Rotate(TreapNode* &o, int d){
    
    
    TreapNode* p = o->son[d ^ 1];
    o->son[d ^ 1] = p->son[d];
    p->son[d] = o;
    o->Update();
    p->Update();
    o = p;
}
  • 更新节点数要先更新旧根,再更新新根

插入操作

  • 解决了旋转,插入就简单的多了,每次插入一个数,首先根据value确定位置(排序二叉树),确定位置以后,随机给定Rank值,如果根节点Rank不是最大,则需要旋转
void Insert(TreapNode* &o, int k){
    
    
    if(o == NULL){
    
    
        o = new TreapNode();
        o->value = k;
        o->Rank = rand();
        o->son[0] = o->son[1] = NULL;
        o->sz = 1;
        o->cnt = 1;
    }else{
    
    
        int d = o->cmp(k);
        if(d != -1){
    
    
            Insert(o->son[d], k);
            if(o->Rank < o->son[d]->Rank) Rotate(o, d ^ 1);
        }else o->cnt++;
    }
    o->Update();
}

删除操作

  • 先看这个值在哪棵子树,然后要看这个值是否有多个,如果有多个,cnt–即可,注意update;如果只有一个,那么需要删除该节点,但是不能直接删除,需要判断左右孩子的优先级保证树的平衡,如果左孩子优先级高,那么节点右旋,此时左孩子转到根,原来的根转到右子树,所以需要递归在右子树中删除根节点
  • 如果该节点不是左孩子右孩子都存在,那么直接把存在的那个孩子移动到根,然后直接删除原来的根节点即可,最后如果根节点不是空指针,那么需要update
void Remove(TreapNode* &o, int k){
    
    
    int d = o->cmp(k);
    if(d == -1){
    
    
        if(o->cnt > 1){
    
    
            o->cnt--;
            o->Update();
            return;
        }
        TreapNode* u = o;
        if(o->son[0] != NULL && o->son[1] != NULL){
    
    
            int d2 = o->son[0]->Rank > o->son[1]->Rank ? 1 : 0;
            Rotate(o, d2);
            Remove(o->son[d2], k);
        }else{
    
    
            if(o->son[0] == NULL) o = o->son[1];
            else o = o->son[0];
            delete u;
        }
    }else{
    
    
        Remove(o->son[d], k);
    }
    if(o != NULL) o->Update();
}

第K大

  • 在这里,我们设定的sz派上了场,从根节点出发,如果K小于右子树的节点数,那么就在右子树里面找第K大(排序二叉树);如果K在右子树节点值的个数范围内,那么就是根节点对应的value;否则就在左子树里面找第(K - 右子树的sz - 出现次数)大,当然需要再处理一些特殊的情况比如越界
int Get_Kth(TreapNode* o, int k){
    
    
    if(o == NULL || k <= 0 || k > o->sz) return 0;
    int s = (o->son[1] == NULL ? 0 : o->son[1]->sz);
    if(k >= s + 1 && k <= s + o->cnt) return o->value;
    else if(k <= s) return Get_Kth(o->son[1], k);
    else return Get_Kth(o->son[0], k - s - o->cnt);
}

K是第几大

  • 如果K不在树上,那么返回-1(有些题目有特殊要求),否则递归查找即可
int Get_Xrank(TreapNode* o, int k){
    
    
    if(o == NULL) return -1;
    int d = o->cmp(k);
    if(d == -1) return o->son[1] == NULL ? 1 : o->son[1]->sz + 1;
    else if(d == 1) return Get_Xrank(o->son[1], k);
    else{
    
    
        int tmp = Get_Xrank(o->son[0], k);
        if(tmp == -1) return -1;
        return tmp + (o->son[1] == NULL ? o->cnt : o->son[1]->sz + o->cnt); 
    }
}

找前驱和后继

  • 一般对于K的前驱的定义是小于K的最大值,那么对于排序二叉树,从根节点出发,如果根节点value小于K,那么更新前驱,转向右孩子,递归查找可能的更大的前驱;如果根节点value大于或等于K,说明value大了,那么递归查找左子树直到找到答案,找不到就返回一个特殊标记
int Get_Pre(TreapNode* o, int k){
    
    
    int pre = -INF;
    while(o != NULL){
    
    
        if(o->value  >= k){
    
    
            o = o->son[0];
        }else{
    
    
            pre = o->value;
            o = o->son[1];
        }
    }
    return pre;
}
  • 后继和前驱类似
int Get_Next(TreapNode* o, int k){
    
    
    int Next = INF;
    while(o != NULL){
    
    
        if(o->value <= k){
    
    
            o = o->son[1];
        }else{
    
    
            Next = o->value;
            o = o->son[0];
        }
    }
    return Next;
}
  • 到这里Treap树的概念和功能基本完成

练习

普通平衡树

  • 模板题,但是这里面找的是第K小
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
struct TreapNode{
    
    
    int value;
    int Rank;
    int sz;
    int cnt;
    TreapNode* son[2];
    int cmp(int k)const{
    
    
        if(k == value) return -1;
        return k < value ? 0 : 1;
    }
    void Update(){
    
    
        sz = cnt;
        if(son[0] != NULL) sz += son[0]->sz;
        if(son[1] != NULL) sz += son[1]->sz;
    }
};
void Rotate(TreapNode* &o, int d){
    
    
    TreapNode* p = o->son[d ^ 1];
    o->son[d ^ 1] = p->son[d];
    p->son[d] = o;
    o->Update();
    p->Update();
    o = p;
}
void Insert(TreapNode* &o, int k){
    
    
    if(o == NULL){
    
    
        o = new TreapNode();
        o->son[0] = o->son[1] = NULL;
        o->value = k;
        o->Rank = rand();
        o->sz = 1;
        o->cnt = 1;
    }else{
    
    
        int d = o->cmp(k);
        if(d == -1){
    
    
            o->cnt++;
        }else{
    
    
            Insert(o->son[d], k);
            if(o->Rank < o->son[d]->Rank) Rotate(o, d ^ 1);
        }
    }
    o->Update();
}
void Remove(TreapNode* &o, int k){
    
    
    int d = o->cmp(k);
    if(d == -1){
    
    
        if(o->cnt > 1){
    
    
            o->cnt--;
            o->Update();
            return;
        }else{
    
    
            TreapNode* u = o;
            if(o->son[0] != NULL && o->son[1] != NULL){
    
    
                int d2 = o->son[0]->Rank > o->son[1]->Rank ? 1 : 0;
                Rotate(o, d2);
                Remove(o->son[d2], k);
            }else{
    
    
                if(o->son[0] == NULL) o = o->son[1];
                else o = o->son[0];
                delete u;
            }
        }
    }else Remove(o->son[d], k);
    if(o != NULL) o->Update();
}
int Get_Pre(TreapNode* o, int k){
    
    
    int pre = -INF;
    while(o != NULL){
    
    
        if(o->value < k){
    
    
            pre = o->value;
            o = o->son[1];
        }else o = o->son[0];
    }
    return pre;
}
int Get_Next(TreapNode* o, int k){
    
    
    int Next = INF;
    while(o != NULL){
    
    
        if(o->value > k){
    
    
            Next = o->value;
            o = o->son[0];
        }else o = o->son[1];
    }
    return Next;
}
int Get_Kth(TreapNode* o, int k){
    
    
    if(o == NULL || o->sz < k || k <= 0) return -1;
    int s = o->son[0] == NULL ? 0 : o->son[0]->sz;
    if(k >= s + 1 && k <= s + o->cnt) return o->value;
    else if(k <= s) return Get_Kth(o->son[0], k);
    else return Get_Kth(o->son[1], k - o->cnt - s);
}
int Get_Krank(TreapNode* o, int k){
    
    
    if(o == NULL) return -1;
    int d = o->cmp(k);
    if(d == -1) return o->son[0] == NULL ? 1 : o->son[0]->sz + 1;
    else if(d == 0) return Get_Krank(o->son[0], k);
    else{
    
    
        int tmp = Get_Krank(o->son[1], k);
        if(tmp == -1) return -1;
        else return tmp + (o->son[0] == NULL ? o->cnt : o->son[0]->sz + o->cnt);
    }
}
int main(){
    
    
    int n, op, x;
    TreapNode* root = NULL;
    scanf("%d", &n);
    while(n--){
    
    
        scanf("%d%d", &op, &x);
        if(op == 1) Insert(root, x);
        else if(op == 2) Remove(root, x);
        else if(op == 3) printf("%d\n", Get_Krank(root, x));
        else if(op == 4) printf("%d\n", Get_Kth(root, x));
        else if(op == 5) printf("%d\n", Get_Pre(root, x));
        else printf("%d\n", Get_Next(root, x));
    }
    return 0;
}

加强的模板题

  • 主要是卡线段树,使用Treap无影响
  • 相对于上一道题需要改一个函数,因为x可能不存在,所以在查询
    x是第几大的时候如果没查到不能返回-1,而是返回1(考虑root为空指针)
int Get_Xrank(TreapNode* o, int k){
    
    
    if(o == NULL) return 1;
    int d = o->cmp(k);
    if(d == -1) return o->son[0] == NULL ? 1 : o->son[0]->sz + 1;
    else if(d == 0) return Get_Xrank(o->son[0], k);
    else{
    
    
        int tmp = Get_Xrank(o->son[1], k);
        // if(tmp == -1) return -1;
        return tmp + (o->son[0] == NULL ? o->cnt : o->son[0]->sz + o->cnt); 
    }
}

poj3481

  • 同样的模板题,这里面的priority可以作为value,最后输出Rank,反着输入直接套模板
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
struct TreapNode{
    
    
    int value;
    int Rank;
    int sz;
    int cnt;
    TreapNode* son[2];
    int cmp(int k)const{
    
    
        if(k == value) return -1;
        return k < value ? 0 : 1;
    }
    void Update(){
    
    
        sz = cnt;
        if(son[0] != NULL) sz += son[0]->sz;
        if(son[1] != NULL) sz += son[1]->sz;
    }
};
void Rotate(TreapNode* &o, int d){
    
    
    TreapNode* p = o->son[d ^ 1];
    o->son[d ^ 1] = p->son[d];
    p->son[d] = o;
    o->Update();
    p->Update();
    o = p;
}
void Insert(TreapNode* &o, int k, int priority){
    
    
    if(o == NULL){
    
    
        o = new TreapNode();
        o->son[0] = o->son[1] = NULL;
        o->value = k;
        o->Rank = priority;
        o->sz = 1;
        o->cnt = 1;
    }else{
    
    
        int d = o->cmp(k);
        if(d == -1){
    
    
            o->cnt++;
        }else{
    
    
            Insert(o->son[d], k, priority);
            if(o->Rank < o->son[d]->Rank) Rotate(o, d ^ 1);
        }
    }
    o->Update();
}
int FIND_MAX(TreapNode* &o){
    
    
    if(o == NULL) return 0;
    if(o->son[1] == NULL){
    
    
        int ans = o->Rank;
        TreapNode* u = o;
        o = o->son[0];
        delete u;
        u = NULL;
        return ans;
    }
    return FIND_MAX(o->son[1]);
}
int FIND_MIN(TreapNode* &o){
    
    
    if(o == NULL) return 0;
    if(o->son[0] == NULL){
    
    
        int ans = o->Rank;
        TreapNode* u = o;
        o = o->son[1];
        delete u;
        u = NULL;
        return ans;
    }
    return FIND_MIN(o->son[0]);
}
int main(){
    
    
    int n, k, p;
    TreapNode* root = NULL;
    while(~scanf("%d", &n)&&n){
    
    
        if(n == 1){
    
    
            scanf("%d%d", &k, &p);
            Insert(root, p, k);
        }else if(n == 2){
    
    
            printf("%d\n", FIND_MAX(root));
        }else printf("%d\n", FIND_MIN(root));
    }
    return 0;
}

营业额统计 洛谷
营业额统计 BZOJ

  • 两个oj上面这道题题面相同,这个题显然是要求Treap树上离自己value最近的差的绝对值加和即为答案,这里需要改一下前驱和后继,因为这里面可以相等
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
struct TreapNode{
    
    
    int value;
    int Rank;
    int sz;
    int cnt;
    TreapNode* son[2];
    int cmp(int k)const{
    
    
        if(k == value) return -1;
        return k < value ? 0 : 1;
    }
    void Update(){
    
    
        sz = cnt;
        if(son[0] != NULL) sz += son[0]->sz;
        if(son[1] != NULL) sz += son[1]->sz;
    }
};
void Rotate(TreapNode* &o, int d){
    
    
    TreapNode* p = o->son[d ^ 1];
    o->son[d ^ 1] = p->son[d];
    p->son[d] = o;
    o->Update();
    p->Update();
    o = p;
}
void Insert(TreapNode* &o, int k){
    
    
    if(o == NULL){
    
    
        o = new TreapNode();
        o->son[0] = o->son[1] = NULL;
        o->value = k;
        o->Rank = rand();
        o->sz = 1;
        o->cnt = 1;
    }else{
    
    
        int d = o->cmp(k);
        if(d == -1){
    
    
            o->cnt++;
        }else{
    
    
            Insert(o->son[d], k);
            if(o->Rank < o->son[d]->Rank) Rotate(o, d ^ 1);
        }
    }
    o->Update();
}
int Get_Pre(TreapNode* o, int k){
    
    
    int pre = -INF;
    while(o != NULL){
    
    
        if(o->value  > k){
    
    
            o = o->son[0];
        }else{
    
    
            pre = o->value;
            o = o->son[1];
        }
    }
    return pre;
}
int Get_Next(TreapNode* o, int k){
    
    
    int Next = INF;
    while(o != NULL){
    
    
        if(o->value < k){
    
    
            o = o->son[1];
        }else{
    
    
            Next = o->value;
            o = o->son[0];
        }
    }
    return Next;
}
int main(){
    
    
    int n, k, p;
    TreapNode* root = NULL;
    scanf("%d", &n);
    int ans = 0;
    while(n--){
    
    
        scanf("%d", &k);
        if(root == NULL) ans += k;
        else{
    
    
            ans += min(k - Get_Pre(root, k), Get_Next(root, k) - k);
        }
        Insert(root, k);
    }
    printf("%d", ans);
    return 0;
}
  • 洛谷上面没问题,但是BZOJ就是0分,看了kuangbin博客发现输入里面需要加这样一句话
if(scanf("%d", &k) == EOF) k = 0;
  • 搞不懂~~~
    郁闷的出纳员 洛谷
  • 这道题需要使用一个delta来保存当前工资变化情况,有一个技巧是插入的时候带着delta一起插入,因为delt存储的是所有人的工资变化,这样便于统计
  • 当扣钱的时候需要判断有没有员工退出,如果有,把他们删除掉
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
struct TreapNode{
    
    
    int value;
    int Rank;
    int sz;
    int cnt;
    TreapNode* son[2];
    int cmp(int k)const{
    
    
        if(k == value) return -1;
        return k < value ? 0 : 1;
    }
    void Update(){
    
    
        sz = cnt;
        if(son[0] != NULL) sz += son[0]->sz;
        if(son[1] != NULL) sz += son[1]->sz;
    }
};
void Rotate(TreapNode* &o, int d){
    
    
    TreapNode* p = o->son[d ^ 1];
    o->son[d ^ 1] = p->son[d];
    p->son[d] = o;
    o->Update();
    p->Update();
    o = p;
}
void Insert(TreapNode* &o, int k){
    
    
    if(o == NULL){
    
    
        o = new TreapNode();
        o->son[0] = o->son[1] = NULL;
        o->value = k;
        o->Rank = rand();
        o->sz = 1;
        o->cnt = 1;
    }else{
    
    
        int d = o->cmp(k);
        if(d == -1){
    
    
            o->cnt++;
        }else{
    
    
            Insert(o->son[d], k);
            if(o->Rank < o->son[d]->Rank) Rotate(o, d ^ 1);
        }
    }
    o->Update();
}
int Get_Kth(TreapNode* o, int k){
    
    
    if(o == NULL || o->sz < k || k <= 0) return -1;
    int s = o->son[1] == NULL ? 0 : o->son[1]->sz;
    if(k >= s + 1 && k <= s + o->cnt) return o->value;
    else if(k <= s) return Get_Kth(o->son[1], k);
    else return Get_Kth(o->son[0], k - o->cnt - s);
}
int del(TreapNode* &o, int k){
    
    
    if(o == NULL) return 0;
    int t;
    if(o->value < k){
    
    
        t = o->son[0] == NULL ? o->cnt : o->son[0]->sz + o->cnt;
        o = o->son[1];
        return t + del(o, k);
    }else{
    
    
        t = del(o->son[0], k);
        o->sz -= t;
        return t;
    }
}
int main(){
    
    
    int n, k, p, MIN;
    int delta = 0;
    int leave = 0;
    char ch[3];
    TreapNode* root = NULL;
    scanf("%d%d", &n, &MIN);
    while(n--){
    
    
        scanf("%s%d", ch, &k);
        if(ch[0] == 'I'){
    
    
            if(k >= MIN) Insert(root, k - delta);
        }else if(ch[0] == 'A'){
    
    
            delta += k;
        }else if(ch[0] == 'S'){
    
    
            delta -= k;
            leave += del(root, MIN - delta);
        }else{
    
    
            if(root == NULL || k > root->sz) printf("-1\n");
            else printf("%d\n", Get_Kth(root, k) + delta);
        }
    }
    printf("%d", leave);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/roadtohacker/article/details/113380280