Treap(树堆)
AVL:依靠左右节点的深度控制平衡
红黑树:依靠链中黑节点数相同和红节点不可相邻控制平衡
SBT:依靠左右节点和对应节点的子节点的大小关系控制平衡
TREAP:树堆依靠随机数实现平衡
原理:在每一个节点中加入一个随机域,依靠随机数实现堆的性质,控制树的平衡,TREAP即是一颗二叉树也是一个堆。
特点:简单,容易实现![]()
Treap节点数据:
structnode{
intval,fix; //val:节点数据,fix:随机数
node *ch[2]; //左右孩子
}*root=NULL; //根节点
TREAP的主要操作:
1、旋转
2、查找、最大值、最小值
3、插入、删除
4、分离、合并
5、前驱、后继
6、第X个值
7、懒惰标志的应用
TREAP的旋转很简单,只有两种,左旋和右旋
代码:
void rotate(node * &cur,int f){
node *p=cur->ch[!f];
cur->ch[!f]=p->ch[f];
p->ch[f]=cur;
cur=p;
}
旋转节点为高处点,f代表旋转方向,1代表向右旋转,0代表向左
Treap节点插入及初始化:
- Treap查找最大值、最小值与普通二叉树查找方式相同
- Treap节点插入时先按照二叉排序树插入,然后按照随机数维护堆的性质
void insert(node * &cur,int x){
if(!cur)cur=newnode(x);
else{
int bj=cur->val<x;
insert(cur->ch[bj],x);
if(cur->ch[bj]->fix<cur->fix)
rotate(cur,!bj);
}
}
node * newnode(int x){
node * cur=new node;
cur->val=x;
cur->fix=rand();
cur->ch[0]=cur->ch[1]=NULL;
return cur;
}
情况一:左右子节点全部为NULL,则该节点直接设为NULL即可
情况二:左右子节点有一个为NULL,则将该节点设为它的非NULL子节点
情况三:左右子节点都非NULL,通过旋转成以上情况再删除
void del(node * &cur,int x){
if(!cur)return;
if(cur->val > x)del(cur->ch[0],x); //在左子树中查找删除
else if(cur->val < x)del(cur->ch[1],x); //在右子树中查找删除
else{ //删除当前节点
if(!cur->ch[0]||!cur->ch[1]){ //子树中有NULL
node *t=cur;
cur=(!cur->ch[0])?cur->ch[1]:cur->ch[0];
delete t;
}
else { //子树中无空
int bj=cur->ch[0]->fix<cur->ch[1]->fix;
rotate(cur,bj);
del(cur->ch[bj],x);
}
}
}
TREAP分离和合并: 分离:要把一个Treap按大小分成两个Treap,只要在需要分开的位置加一个虚拟节点,然后旋至根节点删除,左右两个子树就是得出的两个Treap了。根据二叉搜索树的性质,这时左子树的所有节点都小于右子树的节点。时间相当于一次插入操作的复杂度,也就是O(logn)
合并:TREAP合并的条件为第一颗TREAP的每一个节点的值小于第二颗的每一个节点的值,则增加一个虚拟节点,左右孩子分别为这两颗TREAP,在删除该虚拟节点。
Treap定值的前驱和后继:求前驱的基本思想:贪心逼近法。在树中查找,一旦遇到一个不大于这个元素的值的节点,更新当前的最优的节点,然后在当前节点的右子树中继续查找,目的是希望能找到一个更接近于这个元素的节点。如果遇到大于这个元素的值的节点,不更新最优值,节点的左子树中继续查找。直到遇到空节点,查找结束,当前最优的节点的值就是要求的前驱。求后继的方法与上述相似。
node* pred(node* cur,node * f,int x)
node* secc(node* cur,node * f,int x)
Cur:当前节点,f记录前驱和后即,x标准值
node * pred(node * cur,node * f,int x){
if(!cur)return f;
if(x<cur->val)return pred(cur->ch[0],f,x);
return pred(cur->ch[1],cur,x);
}
node * secc(node * cur,node * f,int x){
if(!cur)return f;
if(cur->val<x)return secc(cur->ch[1],f,x);
return secc(cur->ch[0],cur,x);
}
//主程序中调用:
Node *p = Pred(root,null,e);
Node *s = Succ(root,null,e);
TREAP第x值和懒惰标志: 求第x值节点需要增加size;方法与其他二叉查找树相同。
node * find(node * cur,int x){
int y=ssize(cur->ch[0]);
if(y==x-1)return cur;
else if(y>=x)return find(cur->ch[0],x);
else return find(cur->ch[1],x-1-y);
}
懒惰标志的下传和数据更新:int ssize(node * cur){
return cur?cur->size:0;
}
void update(node * cur){
if(!cur)return;
cur->size=ssize(cur->ch[0])+ssize(cur->ch[1])+1;
}
void down(node * cur){
if(cur->delta){
if(cur->ch[0]){
cur->ch[0]->delta+=cur->delta;
cur->ch[0]->val+=cur->delta;
}
if(cur->ch[1]){
cur->ch[1]->delta+=cur->delta;
cur->ch[1]->val+=cur->delta;
}
cur->delta=0;
}
}
TREAP值在树中的排序:int px(Node *cur,int x,int js)
{
if (x ==cur->val)
return cur->ch[0]->size + js + 1; //返回元素的排名
else if (x < cur->val)
return px(cur->ch[0],x,js); //在左子树中查找
else
return px(cur->ch[1],x,js + cur->ch[0]->size + cur->cnt);
//在右子树中查找
}