2015-03-16 20:13:07
总结:
初学Treap,来小结一下。(部分摘自上交红书:算法与实现)
struct Treap{
int root , tcnt; //tcnt 为treap结点数
int key[] , pro[] , son[][2]; //key保存键值,pro保存优先级,son保存儿子编号(son[x][0] , son[x][1] 分别表示 x 的左右儿子)
(int cnt[] , sz[] ... //根据题目,增加的数据成员)
(void Update(int x) {}... //根据题目,增加的成员函数)
void Rotate(int &x,int t) {} //x为当前根的位置,t指示了旋转种类
void Insert(int &x,int k) {} //x为当前位置,k为要插入的键值
void Erase(int &x,int k) {} //x为当前位置,k为要删除的键值
};
功能简述:
(1)旋转函数:
(i)左旋转,根与左儿子互换位置(左儿子上提),并将左儿子的右儿子挂给根。(这可以看成给根下降作的一个“补偿”)
(ii)右旋转,根与右儿子互换位置(右儿子上提),并将右儿子的左儿子挂给根。
(2)插入函数:在插入的时候,需要判断插入后是否符合堆的性质,不符合则需要旋转。(注意这种旋转是递归到底层后回溯过程中不断旋转的)
(3)删除函数:在保证其他节点满足堆的性质的情况下,将待删除节点旋转到叶子节点即可。
拓展:
可以通过将一个将节点的优先值设为-INF,来强制提到根,不过可能会破坏 treap 的期望深度。
1 struct Treap{ 2 int root,tcnt; 3 int key[MAXN],pro[MAXN],cnt[MAXN],sz[MAXN],son[MAXN][2]; 4 void clear(){ 5 root = 0; 6 tcnt = 0; 7 pro[0] = INF; //pro[0]用来表示空树的优先级 8 sz[0] = 0; 9 } 10 void Update(int x){ 11 sz[x] = cnt[x] + sz[son[x][0]] + sz[son[x][1]]; 12 } 13 void Rotate(int &x,int t){ 14 int y = son[x][t]; 15 son[x][t] = son[y][1 - t]; 16 son[y][1 - t] = x; 17 Update(x); 18 Update(y); 19 x = y; 20 } 21 void _Insert(int &x,int k){ 22 if(x){ //当前是非空树 23 if(key[x] == k) ++cnt[x]; //如果值重复,该数的数量加1 24 else{ 25 int t = k > key[x]; //用 t 来比较根值和待插入值 26 _Insert(son[x][t],k); 27 if(pro[son[x][t]] < pro[x]) Rotate(x,t); //如果插入以后导致堆的性质不满足,则旋转 28 } 29 } 30 else{ //当前是空树,那么直接插入作为根节点 31 x = ++tcnt; 32 key[x] = k; 33 cnt[x] = 1; 34 pro[x] = rand(); //随机化优先级 35 son[x][0] = son[x][1] = 0; 36 } 37 Update(x); 38 } 39 void _Erase(int &x,int k){ 40 if(key[x] == k){ //找到待删除值 41 if(cnt[x] > 1) cnt[x]--; 42 else{ 43 if(son[x][0] == 0 && son[x][1] == 0){ //待删除节点无左/右儿子 44 x = 0; 45 return; 46 } 47 int t = pro[son[x][0]] > pro[son[x][1]]; //用到了空树的优先级 48 //没有左儿子t = 1 , 没有右儿子t = 0 49 Rotate(x,t); //优先级小的提上来 50 _Erase(x,k); //旋转后待删除节点下移(x已经被赋值为儿子),继续向下删除 51 } 52 } 53 else _Erase(son[x][k > key[x]],k); //未发现 54 Update(x); 55 } 56 void Insert(int k){ 57 _Insert(root,k); 58 } 59 void Erase(int k){ 60 _Erase(root,k); 61 } 62 }tp;