刚刚跟着EM-LGH大佬学了非旋转Treap

非常庆幸不用再写万恶的rotate了(来自高级数据结构的恶意)

来记一下

简单来说,\(Tree_{二叉搜索树} * Heap_堆 = Treap_{平衡树}\)

这显然不是袁隆平爷爷干的

二叉搜索树←不懂请戳这里

显然这两样东西有各自的排列顺序——左小右大以及根小(大)儿子大(小)

对于寻找答案来讲,二叉搜索树更加方便

那么堆用来干嘛呢

很简单,用来达到期望平衡

怎么实现呢

通过另一个关键字

为什么是“期望”平衡呢

因为是通过随机的关键字啊!

上面说过了,二叉搜索树管答案,堆管时间

李云龙:“你管生活,我管军事”

如何让随机的关键字满足堆的性质,同时节点的值满足二叉搜索树的性质呢

然而这个玩意十分难写且难理解。。。

所以就出现了……

它与旋转的Treap很相似

但是它是基于分裂和合并两个基本操作而不是旋转

-define表+struct,请对照此表理解代码-

  1. #define lson t[x].ls
  2. #define rson t[x].rs
  3. #define si t[x].size
  4. #define ra t[x].ran
  5. #define lss t[t[x].ls].size
  6. #define rss t[t[x].rs].size
  7. #define va t[x].val
  8. //-------------------------
  9. struct node
  10. {
  11. int val, size, ls, rs, ran;
  12. }t[100001];

指定一个val,将值∈[0, val]的节点与值∈(val, +∞)的节点分成两棵树

实现过程和寻找后继的过程很像

  1. void split(int x, int &l, int &r, int val)
  2. {
  3. if(!x)
  4. {
  5. l = r = 0;
  6. return;
  7. }
  8. if(va <= val) l = x, split(t[x].rs, t[l].rs, r, val);//当前值比val小或等于val,则将它与它的左子树全部划分到第一棵树,继续寻找它的右子树
  9. else r = x, split(t[x].ls, l, t[r].ls, val);//反之,则将它与它的右子树划分到第二棵树,寻找它的左子树
  10. pushup(x);//不要忘记更新size
  11. }

分裂的反过程

要求合并的A树与B树中\(A_{max} < B_{min}\)

  1. void merge(int &x, int a, int b)
  2. {
  3. if(!a||!b)
  4. {
  5. x = a + b;
  6. return;
  7. }
  8. if(t[a].ran < t[b].ran) x = a, merge(t[x].rs, t[a].rs, b);//随机值在这里用,用来在合并时维护堆的性质
  9. else x = b, merge(t[x].ls, a, t[b].ls);
  10. pushup(x);//更新!
  11. }

基于分裂和合并

\(val – 1\)处分裂->合并节点Z与树A->合并树A与树B

  1. void insert(int val)
  2. {
  3. int x = 0, y = 0, z = 0;
  4. newnode(z, val);
  5. split(root, x, y, val - 1);
  6. merge(x, x, z);
  7. merge(root, x, y);
  8. }

和插入很像

将大树在\(val – 1\)处分裂成AB->将树B在\(val\)处分裂成BC->合并树A与树C

  1. void del(int val)
  2. {
  3. int x = 0, y = 0, z = 0;
  4. split(root, x, y, val);
  5. split(x, x, z, val - 1);
  6. merge(z, t[z].ls, t[z].rs);//这里是只删除一个的操作,全部删除请忽略本行和下一行
  7. merge(x, x, z);
  8. merge(root, x, y);
  9. }

和插入很像

\(val-1\)处分裂->输出A的size

  1. void ask_rank(int v)
  2. {
  3. int x = 0, y = 0;
  4. split(root, x, y, v - 1);
  5. cout << si + 1;
  6. merge(root, x, y);
  7. }

相当于反着问排名

  1. void ask_num(int x, int kth)
  2. {
  3. while(lss + 1 != kth)
  4. {
  5. if(lss >= kth) x = lson;
  6. else kth -= (lss + 1), x = rson;
  7. }
  8. cout << va;
  9. }

\(v-1\)处分裂->询问A中最大(第size小)->合并

  1. void ask_fr(int v)
  2. {
  3. int x = 0, y = 0;
  4. split(root, x, y, v - 1);
  5. ask_num(x, si);
  6. merge(root, x, y);
  7. }

与前驱相反

\(v\)处分裂->询问B中第一小->合并

  1. void ask_ba(int v)
  2. {
  3. int x = 0, y = 0;
  4. split(root, x, y, v);
  5. ask_num(y, 1);
  6. merge(root, x, y);
  7. }

由于它是期望平衡的,所以它的所有操作都在\(O(logN)\)左右。

版权声明:本文为H2SO4原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/H2SO4/p/12170467.html