fhq-Treap原理

Posted ctyakwf

tags:

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

fhq-Treap是一种非常优秀的无旋平衡树。下面我们来介绍他的函数原理

我们先介绍他的两个重要函数

1.spilt(分割函数)

我们将整棵树按照值的大小分割成x,y两棵树,x中的值都是小于等于key值的

这样我们就可以通过递归操作来解析,下面看具体代码

void spilt(int p,int key,int &x,int &y){ //按key分裂 
    if(!p)   //分裂完毕赋空值 
    x=y=0;
    else{
        if(tr[p].key<=key){  //满足左子树条件,所有值小于key 
            x=p;        
            spilt(tr[p].r,key,tr[p].r,y);//递归右边节点 
        }
        else{
            y=p;
            spilt(tr[p].l,key,x,tr[p].l);//同理,递归左边 
        }
        pushup(p); //无论哪个值取到p,只需要更新p,那就更新完了节点 
    }
}

重点就是如果当前值小于等于key,那么他的左子树都是小于等于key的,右子树有可能也存在,因此将x=p,再将右子树传递,反之同理,结束点是目前这个是空树

2.merge(合并函数)

我们考虑将两个已经分割好的树合并起来,有几个注意点

递归结束点就是存在一棵树已经合并完了,那么只需要返回x+y就是我们当前所需的根节点值

下面我们先来看代码

int merge(int x,int y){
    if(!x||!y)
    return x+y;//返回不为0的值 
    if(tr[x].val>=tr[y].val){  //根据二叉堆的性质 
        tr[x].r=merge(tr[x].r,y); //x中值都比y中的小 
        pushup(x);
        return x;  //父节点是x 
    }
    else{
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}

如果左边节点的索引值大于右边,我们又可以知道根据二叉树和二叉堆的共同作用下,x中的树要比y中的树小,但是x又要是根节点,那么y树就要在x的右子树中,反之同理

注意合并完后要更新节点的个数大小

下面我们来分析这两个函数如何完成平衡树的操作

众所周知,平衡树的操作为了获取以下功能

1.insert函数

我们需要将一个新值插入树当中,那么我们可以先将树按这个新值key来切割,左边都是小于等于key,然后我们把x和key生成的节点合并,再把x和y合并就可

int insert(int key){
    spilt(root,key,x,y);  //按key值分离 
    root=merge(merge(x,get(key)),y);//合并回来更新root 
}

2.remove函数(删除一个节点)

我们先按key分离x,z

再按key-1分离x,y

这样y树中值就全都是key了,然后我们将y中左子树和右子树合并,就成功删除了根节点

void remove(int key){
    spilt(root,key,x,z);
    spilt(x,key-1,x,y);
    y=merge(tr[y].l,tr[y].r);
    root=merge(merge(x,y),z);
}

 

3.getrank(根据值找排名)

我们只需要按key-1切割树,那么排名就是x树的节点+1

非常简单

int getrank(int key){
    spilt(root,key-1,x,y);
    cout<<tr[x].size+1<<endl;
    root=merge(x,y);
}

4.getnum(根据排名找值)

这个操作就复杂一些了,我们这次使用while循环求解

我们知道如果左子树的排名+1等于rank,那么我们就找到这个值了

如果不是,如果左子树排名大于等于rank,我们就去左子树找,否则就去右子树

本题当中一定能找到答案,因为题目说明

int getnum(int rank){
    int p=root;
    while(p){
        if(tr[tr[p].l].size+1==rank)
        break;
        else if(tr[tr[p].l].size>=rank)
        p=tr[p].l;
        else{
            rank-=tr[tr[p].l].size;
            rank-=1;
            p=tr[p].r;
        }
    }
    cout<<tr[p].key<<endl;
}

5.寻找前驱(指的是比key小的最大值)

这只需要先按key-1切割,然后一直递归右节点就能找到答案

6.寻找后继

同上一直递归左节点就行

int pre(int key){
    spilt(root,key-1,x,y);
    int p=x;
    while(tr[p].r){
        p=tr[p].r;
    }
    cout<<tr[p].key<<endl;
    root=merge(x,y);
}
int nxt(int key){
    spilt(root,key,x,y);
    int p=y;
    while(tr[p].l){
        p=tr[p].l;
    }
    cout<<tr[p].key<<endl;
    root=merge(x,y);
}

--------------------------------------结束线

完结撒花,至此,fhq-Treap解决了所有普通平衡树需要的操作,非常完美,代码简单,没有旋转,神牛果然是神牛

下面我给出洛谷p3369的AC代码,这也是一道模板题

我们学会了基础操作,但是还远远不够,继续加油,解决更难的题目!!!

技术图片
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map> 
#include<vector> 
using namespace std;
typedef  long long ll;
const int N=5e5+10;
const int inf=0x3f3f3f3f;
int root,idx;
int x,y,z;
struct node{
    int l,r;
    int key;
    int val;
    int size;
}tr[N];
int get(int key){
    tr[++idx].key=key;
    tr[idx].size=1;
    tr[idx].val=rand();
    return idx;
}
void pushup(int u){
    tr[u].size=tr[tr[u].l].size+tr[tr[u].r].size+1;
}
void spilt(int p,int key,int &x,int &y){ //按key分裂 
    if(!p)   //分裂完毕赋空值 
    x=y=0;
    else{
        if(tr[p].key<=key){  //满足左子树条件,所有值小于key 
            x=p;        
            spilt(tr[p].r,key,tr[p].r,y);//递归右边节点 
        }
        else{
            y=p;
            spilt(tr[p].l,key,x,tr[p].l);//同理,递归左边 
        }
        pushup(p); //无论哪个值取到p,只需要更新p,那就更新完了节点 
    }
}
int merge(int x,int y){
    if(!x||!y)
    return x+y;//返回不为0的值 
    if(tr[x].val>=tr[y].val){  //根据二叉堆的性质 
        tr[x].r=merge(tr[x].r,y); //x中值都比y中的小 
        pushup(x);
        return x;  //父节点是x 
    }
    else{
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}
int insert(int key){
    spilt(root,key,x,y);  //按key值分离 
    root=merge(merge(x,get(key)),y);//合并回来更新root 
}
void remove(int key){
    spilt(root,key,x,z);
    spilt(x,key-1,x,y);
    y=merge(tr[y].l,tr[y].r);
    root=merge(merge(x,y),z);
}
int getrank(int key){
    spilt(root,key-1,x,y);
    cout<<tr[x].size+1<<endl;
    root=merge(x,y);
}
int getnum(int rank){
    int p=root;
    while(p){
        if(tr[tr[p].l].size+1==rank)
        break;
        else if(tr[tr[p].l].size>=rank)
        p=tr[p].l;
        else{
            rank-=tr[tr[p].l].size;
            rank-=1;
            p=tr[p].r;
        }
    }
    cout<<tr[p].key<<endl;
}
int pre(int key){
    spilt(root,key-1,x,y);
    int p=x;
    while(tr[p].r){
        p=tr[p].r;
    }
    cout<<tr[p].key<<endl;
    root=merge(x,y);
}
int nxt(int key){
    spilt(root,key,x,y);
    int p=y;
    while(tr[p].l){
        p=tr[p].l;
    }
    cout<<tr[p].key<<endl;
    root=merge(x,y);
}
int main(){
    int t;
    cin>>t;
    while(t--){
        int opt,x;
        cin>>opt>>x;
        switch(opt){
            case 1:
                insert(x);
                break;
            case 2:
                remove(x);
                break;
            case 3:
                getrank(x);
                break;
            case 4:
                getnum(x);
                break;
            case 5:
                pre(x);
                break;
            case 6:
                nxt(x);
                break;
        }
    }
}
View Code

 

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

算法学习Fhq-Treap(无旋Treap)

模板fhq-treap

fhq-treap

FHQ-treap模板篇

Fhq-Treap 学习笔记

数据结构之fhq-treap