平衡树Treap

二叉查找树

给定一颗二叉树,树上每个节点都带有一个数值,称为该节点的“关键码”,所谓“BST性质”指的是: 1.该节点的关键码不小于它的左子树中任意节点的关键码; 2.该节点的关键码不大于它的右子树中任意节点的关键码。 满足上述性质的二叉树就是一颗“二叉查找树(BST)”。显然,二叉查找树的中序便利是一个关键码单调递增的节点序列

BST的建立

为了避免月结,减少边界情况的特殊判断,一般在一个关键码为正无穷(一个很大的正整数)和一个关键码为负无穷的节点,仅由这两个节点构成的BST就是一颗初始的空BST。

const int MAXN = 99;
struct BST{
	int l,r;
	int val;
}tree[MAXN];
int tot,root,INF = 1<<30;
int New(int val){
	tree[++tot].val = val;
	return tot;	
}
void build(){
	New(-INF),New(INF);
	root = 1;
	tree[1].r = 2;
}

# BST检索 在BST中检索是否存在关键码为val的节点。 设变量p为根节点root,执行以下过程: 1.若p的关键码等于val,则已经找到 2.若p的关键码大于val (1)若p的左节点为空,则说明不存在 (2)若p的做节点不为空,则在p的子树中递归检索 3.若p的关键码小于val (1)若p的右节点为空,则说明不存在 (2)若p的右节点不为空,则在p的子树中递归检索

 int search(int p,int val){
	if(p==0)return 0;
	if(val==tree[p].val)return p;
	return val<tree[p].val?search(tree[p].l,val):search(tree[p].r,val;
}

BST插入

在BST中插入一个新的值val,(假设目前BST中不存在关键码为val的节点)。 与BST的检索过程类似 在发现要走向的p的子节点为空,说明val不存在时,直接建立关键码为val的新节点作为p的子节点。

void insert(int &p,int val){
	if(p==0){
		p = New(val);
		return;
	}
	if(val==tree[p].val)return;
	if(val<tree[p].val)insert(tree[p].l,val);
	else insert(tree[p].r,val);
}

求BST的前驱/后继

val的后继是指在BST的关键码大于val的前提下,关键码最小的节点。 初始化ans为具有正无穷关键码的那个节点的编号。然后,在BST中检索val。在检索的过程中,每经过一个节点,就检查该节点的关键码,判断能否跟新所求的后继ans; 检索完成后可能有三种结果: 1.没有找到val. 此时val的后继就在已经经过的 2.找到了关键码为val的节点p,但是p没有右子树。与上一种情况相同,ans就是所求找到了关键码为val的节点p,且p有右子树,那么从p的右子树出发,一直向左走,就找到了val的后继

int getNext(int val){
	int ans = 2;//tree[2] = INF
	int p = root;
	while(p){
		if(val==tree[p].val){
			if(tree[p].r>0){
				p = tree[p].r;
				while(tree[p].l>0)p = tree[p].l;
				ans = p;
			}
			break;
		}
		if(tree[p].val>val&&tree[p].val<tree[ans].val)ans = p;
		p = val<tree[p].val?tree[p].l:tree[p].r;
	}
	return ans;
}

BST的节点删除

从BST中删除关键码为val的节点 首先,在BST中检索val,得到节点p。 若p的子节点个数小于2,则直接删除p,并令p的子节点代替p的位置,与p的父节点相连。 若p既有左子树,又有右子树,则在BST中求出val的后即节点next,因为next,没有左子树。最后让next节点代替p节点,删除p即可。

void remove(int val){
	int &p = root;
	while(p){
		if(val==tree[p].val)break;
		p = tree[p].val>val?tree[p].l:tree[p].r;
	}
	if(p==0)return;
	if(tree[p].l==0){//没有左子树,用右子树代替p的位置 
		p = tree[p].r;		
	}else if(tree[p].r==0){
		p = tree[p].l;//没有右子树,左子树代替p的位置 
	}else{
		int next = tree[p].r;
		while(tree[next].l>0)next = tree[next].l;
		remove(tree[next].val);//next一定没有左子树,直接删除
		tree[next].l = tree[p].l,tree[next].r  = tree[p].r;//next 代替p的位置 
		p = next;
	}
}

Treap

左旋(zig)右旋(zag)

右旋zig

注意:这里因为实现的原因,没有记录父节点,为方便起见,把作用对象改为旋转前处于父节点位置的节点 假设$x$是$y$的左子节点,$A$和$B$分别是$x$的左右子树,$C$是$y$的右子树。 “右旋”操作是指在保持BST的性质的基础上,把$x$变为$y$的父节点。因为$x$的关键码小于$y$的关键码,所以$y$应该作为$x$的右子节点。 当$x$变成$y$的父节点后,$y$的左子树就空了出来,于是$x$原来的右子树$B$就恰好作为$y$的左子树。 代码:

void zig(int &p){
    int q = tree[p].l;
    tree[p].l = tree[p].r,tree[q].r = p;
    p = q;
}
void zag(int &p){
    int q = tree[p].r;
    tree[p].r = tree[q].r,tree[q].l = p;
    p = q;
}

如何平衡

我们发现如果数据是随机的,那么BST将趋于平衡。Treap的思想就是利用随机来创造平衡条件。 Treap在插入新节点的时候,给节点随机生成一个额外的权值。然后就像二叉堆的插入过程一样,自底向上检查,当某个节点不满足大根对的性质的时候,就执行单旋转,使他与父节点的关系对调。 对于删除操作,,我们就先找到要删除的节点,并把它向下旋转成叶节点,最后直接删除。这样就避免了采取类似普通BST的删除方法可能导致的节点信息更新、堆性质的维护等复杂问题。 For short,treap通过单旋转,在维持关键码满足BST性质的同时,还使每个节点上随机生成的额外权值满足大根对的性质。检索,查找,删除,求前驱后继及删除节点的时间复杂度都是$O(log N)$

eg:bzoj3224

有问题的代码

//
// Created by dhy on 18-12-2.
//
# include <cstdlib>
# include <cstdio>
int read(){
    int x = 0,f = 1;
    static char c = getchar();
    while(c<'0'||c>'9'){ if(c=='-')f = -1;c = getchar(); }
    while(c>='0'&&c<='9'){ x = (x<<1)+(x<<3)+c-'0';c = getchar(); }
    return x*f;
}
const int SIZE = 100010;
struct Treap{
    int l,r;
    int val,dat;//key code and weight
    int cnt,size;//
}tree[SIZE];
int tot,root,INF = 0x7fffffff;
int New(int val){
    tree[++tot].val = val;
    tree[tot].dat = rand();
    tree[tot].cnt = tree[tot].size = 1;
    return tot;
}
void update(int p){
    tree[p].size = tree[tree[p].l].size+tree[tree[p].r].size+tree[p].cnt;
}
void build(){
    New(-INF),New(INF);
    root = 1;
    tree[1].r = 2;
    update(root);
}
int getRankByVal(int p,int val){
    if(p==0)return 0;
    if(val==tree[p].val)return tree[tree[p].l].size+1;
    if(val<tree[p].val)return getRankByVal(tree[p].l,val);
    return getRankByVal(tree[p].r,val)+tree[tree[p].l].size+tree[p].cnt;
}
int getValByRank(int p,int rank){
    if(p==0)return INF;
    if(tree[tree[p].l].size>=rank)return getValByRank(tree[p].l,rank);
    if(tree[tree[p].l].size+tree[p].cnt>=rank)return tree[p].val;
    return getValByRank(tree[p].r,rank-tree[tree[p].l].size-tree[p].cnt);
}
void zig(int &p){//turn to right
    int q = tree[p].l;
    tree[p].l = tree[q].r,tree[q].r = p,p = q;
    update(tree[p].r);update(p);
}
void zag(int &p){//turn to left
    int q = tree[p].r;
    tree[p].r = tree[q].l;
    tree[q].l = p;
    p = q;
    update(tree[p].l);
    update(p);
}
void insert(int &p,int val){
    if(p==0){
        p = New(val);
        return;
    }
    if(val == tree[p].val){
        tree[p].cnt++;
        update(p);
        return;
    }
    if(val<tree[p].val){
        insert(tree[p].l,val);
        if(tree[p].dat<tree[tree[p].l].dat)zig(p);//TODO
    }else{
        insert(tree[p].r,val);
        if(tree[p].dat<tree[tree[p].r].dat)zag(p);//TODO
    }
    update(p);
}
int getPre(int val){
    int ans = 1;
    int p = root;
    while(p){
        if(val==tree[p].val){
            if(tree[p].l>0){
                p = tree[p].l;
                while(tree[p].r>0)p = tree[p].r;//TODO
                ans = p;
            }
            break;
        }
        if(tree[p].val<val&&tree[p].val>tree[ans].val)ans = p;//TODO
        p = val<tree[p].val?tree[p].l:tree[p].r;
    }
    return tree[ans].val;
}
int getNext(int val){
    int ans = 2;
    int p = root;
    while(p){
        if(val==tree[p].val){
            if(tree[p].r>0){
                p = tree[p].r;
                while(tree[p].l>0)p = tree[p].l;//TODO
                ans = p;
            }
            break;
        }
        if(tree[p].val>val&&tree[p].val<tree[ans].val)ans = p;
        p = val<tree[p].val?tree[p].l:tree[p].r;
    }
    return tree[ans].val;
}
void remove(int &p,int val){
    if(p==0)return;
    if(tree[p].val==val){
        if(tree[p].cnt>1){
            tree[p].cnt--;
            update(p);
            return;
        }
    if(tree[p].l||tree[p].r){//TODO
        if(tree[p].r==0||tree[tree[p].l].dat>tree[tree[p].r].dat)
            zig(p),remove(tree[p].r,val);
        else
            zag(p),remove(tree[p].l,val);
        update(p);
    }
	else
        p = 0;
        return;
    }
    val<tree[p].val?remove(tree[p].l,val):remove(tree[p].r,val);
    update(p);
}
int main(){
    build();
    int n = read();
    while(n--){
        int opt,x;
        opt = read(),x = read();
        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;
}