当前位置:   article > 正文

P3369 【模板】普通平衡树(splay 算法)

P3369 【模板】普通平衡树(splay 算法)

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入一个数 x。
  2. 删除一个数 x(若有多个相同的数,应只删除一个)。
  3. 定义排名为比当前数小的数的个数 +1。查询 x 的排名。
  4. 查询数据结构中排名为 x 的数。
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)。
  6. 求 x 的后继(后继定义为大于 x,且最小的数)。

对于操作 3,5,6,不保证当前数据结构中存在数 x。

输入格式

第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)

输出格式

对于操作 3,4,5,6 每行输出一个数,表示对应答案。

输入输出样例

输入 #1复制

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出 #1复制

106465
84185
492737

说明/提示

【数据范围】
对于 100% 的数据,1≤n≤105,∣x∣≤107

解析:

这时一个思维的跨度:

我们自己构造平衡二叉树,左儿子节点小于父节点,右儿子的全部节点大于父节点。

我们需要对二叉树进行左转和右转。

每次进行转动时替代其相对的儿子。

比如:

右旋时:x的右儿子当作y的左儿子。

这里你们可能会问为什么当y的左儿子呢?

答:因为这是一颗平衡二叉树,左儿子一定小于父节点。

而且x为y的左儿子,x的任意一个儿子一定小于其x的父节点。
左旋同理。

实现代码是用异或操作,刚好就是其儿子转动的对立面。比如:x右旋,x的右儿子为y,这时原本的x的右儿子就要当y的左儿子。这样子x的右儿子不会丢失。又满足平衡二叉树的性质。

实现左旋右旋代码如下:

  1. void rotate(int x) // 可以进行左旋 和右旋
  2. {
  3. //先修 z ;在修 y ;在修 x的儿子节点;在修 x本身
  4. int y = tr[x].fa,z = tr[y].fa, k = tr[y].ch[1] == x; // z -> y -> x 我们需要 将 它转为 z->x->y;
  5. tr[z].ch[tr[z].ch[1] ==y] = x, tr[x].fa = z; // z的 儿子 为 y 替换为 x , 在将 x 的父节点 为 x
  6. tr[y].ch[k] = tr[x].ch[k^1];//比如如果 初始 y的左儿子为 x 当替换完。 y的左儿子空了 ,将x的右儿子给y的做儿子
  7. tr[tr[x].ch[k^1]].fa = y; // 从x下的儿子 替换到 y 下时 ,儿子的父节点要变
  8. tr[x].ch[k^1] = y;//这时x那个替换到 y的儿子空位了。
  9. tr[y].fa = x;
  10. pushup(y);//先push儿子在父节点
  11. pushup(x);
  12. }

下面就是我们如何维护这个平衡二叉树的树高,尽可能使它的高度最小。

我学到的方法是, 当一棵树他是以直线型,折线型时,如下图所示:

当直线型时,先旋转它的父节点,在旋转它自己本身。

当折线型时,先旋转它自己,在旋它自己。

代码如下:

  1. void splay(int x,int k) //k为父节点
  2. {
  3. while(tr[x].fa != k)
  4. {
  5. int y = tr[x].fa,z = tr[y].fa;//从 x起 2个父节点
  6. if(z != k){ // 如果 是 一条 有折线时
  7. //如果 时一条 直线 时 1^1false ,先选 y 在旋 x
  8. (ls(y) == x)^(ls(z)==y) ? rotate(x):rotate(y);
  9. }
  10. rotate(x);
  11. }
  12. if(!k){//等于 0 的时候 才为 根节点
  13. root = x;
  14. }
  15. }

还有一个前驱操作:

先用find函数这个节点在那里。

在遍历它的左儿子的右儿子。到达最右的那个儿子。

后继也是一样。

  1. void find(int v)
  2. {
  3. int x = root;
  4. while(tr[x].ch[v > tr[x].v] && v!=tr[x].v)
  5. {
  6. x = tr[x].ch[v > tr[x].v];
  7. }
  8. splay(x,0);
  9. }
  10. int getpre(int v)
  11. {
  12. find(v);
  13. int x = root;
  14. if(tr[x].v < v){
  15. return x;
  16. }
  17. x = ls(x);
  18. while(rs(x)){
  19. x = rs(x);
  20. }
  21. splay(x,0);
  22. return x;
  23. }
  24. int getsuc(int v)
  25. {
  26. find(v);
  27. int x = root;
  28. if(tr[x].v > v) return x;
  29. x = rs(x);
  30. while(ls(x)) x = ls(x);
  31. splay(x,0);
  32. return x;
  33. }

删除操作:

我们找到它的前驱和后继,再进行把删除的点移到其后继的左儿子上,再进行删除操作。

这个需要画图理解一下。

  1. void del(int v)//删除
  2. {
  3. int pre = getpre(v);
  4. int suc = getsuc(v);
  5. splay(pre,0);
  6. splay(suc,pre);//将它转到左节点方便删除
  7. int del = tr[suc].ch[0];
  8. if(tr[del].cnt > 1)
  9. {
  10. tr[del].cnt--;
  11. splay(del,0);
  12. }
  13. else{
  14. tr[suc].ch[0] = 0;
  15. splay(suc,0);
  16. }
  17. }

刚开始插入操作时,我们要进行哨兵的插入。

完整代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define ls(x) tr[x].ch[0]
  4. #define rs(x) tr[x].ch[1]
  5. const int N = 1100010,INF = (1<<30)-1;
  6. struct node{
  7. int ch[2];
  8. int fa;
  9. int v;
  10. int cnt;
  11. int siz;
  12. void init(int p,int v1){
  13. fa = p;
  14. v = v1;
  15. cnt = siz = 1;
  16. }
  17. }tr[N];
  18. int root = 0,tot = 0;
  19. void pushup(int x)
  20. {
  21. tr[x].siz = tr[ls(x)].siz + tr[rs(x)].siz + tr[x].cnt;
  22. }
  23. void rotate(int x) // 可以进行左旋 和右旋
  24. {
  25. //先修 z ;在修 y ;在修 x的儿子节点;在修 x本身
  26. int y = tr[x].fa,z = tr[y].fa, k = tr[y].ch[1] == x; // z -> y -> x 我们需要 将 它转为 z->x->y;
  27. tr[z].ch[tr[z].ch[1] ==y] = x, tr[x].fa = z; // z的 儿子 为 y 替换为 x , 在将 x 的父节点 为 x
  28. tr[y].ch[k] = tr[x].ch[k^1];//比如如果 初始 y的左儿子为 x 当替换完。 y的左儿子空了 ,将x的右儿子给y的做儿子
  29. tr[tr[x].ch[k^1]].fa = y; // 从x下的儿子 替换到 y 下时 ,儿子的父节点要变
  30. tr[x].ch[k^1] = y;//这时x那个替换到 y的儿子空位了。
  31. tr[y].fa = x;
  32. pushup(y);//先push儿子在父节点
  33. pushup(x);
  34. }
  35. void splay(int x,int k) //k为父节点
  36. {
  37. while(tr[x].fa != k)
  38. {
  39. int y = tr[x].fa,z = tr[y].fa;//从 x起 2个父节点
  40. if(z != k){ // 如果 是 一条 有折线时
  41. //如果 时一条 直线 时 1^1false ,先选 y 在旋 x
  42. (ls(y) == x)^(ls(z)==y) ? rotate(x):rotate(y);
  43. }
  44. rotate(x);
  45. }
  46. if(!k){//等于 0 的时候 才为 根节点
  47. root = x;
  48. }
  49. }
  50. void insert(int v) //插入 节点
  51. {
  52. int x = root,p = 0;
  53. while(x&& tr[x].v != v){ // 弹出 条件 x 为 0节点 找不到 或者 找到当前节点
  54. p = x, x = tr[x].ch[v > tr[x].v];
  55. }
  56. if(x) {
  57. tr[x].cnt++;
  58. }
  59. else{ // 新创建 一个节点
  60. x = ++tot;
  61. tr[p].ch[v>tr[p].v] = x;
  62. tr[x].init(p,v);
  63. }
  64. splay(x,0);
  65. }
  66. void find(int v)
  67. {
  68. int x = root;
  69. while(tr[x].ch[v > tr[x].v] && v!=tr[x].v)
  70. {
  71. x = tr[x].ch[v > tr[x].v];
  72. }
  73. splay(x,0);
  74. }
  75. int getpre(int v)
  76. {
  77. find(v);
  78. int x = root;
  79. if(tr[x].v < v){
  80. return x;
  81. }
  82. x = ls(x);
  83. while(rs(x)){
  84. x = rs(x);
  85. }
  86. splay(x,0);
  87. return x;
  88. }
  89. int getsuc(int v)
  90. {
  91. find(v);
  92. int x = root;
  93. if(tr[x].v > v) return x;
  94. x = rs(x);
  95. while(ls(x)) x = ls(x);
  96. splay(x,0);
  97. return x;
  98. }
  99. void del(int v)//删除
  100. {
  101. int pre = getpre(v);
  102. int suc = getsuc(v);
  103. splay(pre,0);
  104. splay(suc,pre);//将它转到左节点方便删除
  105. int del = tr[suc].ch[0];
  106. if(tr[del].cnt > 1)
  107. {
  108. tr[del].cnt--;
  109. splay(del,0);
  110. }
  111. else{
  112. tr[suc].ch[0] = 0;
  113. splay(suc,0);
  114. }
  115. }
  116. int getrank(int v)
  117. {
  118. insert(v);
  119. int res = tr[tr[root].ch[0]].siz;
  120. del(v);
  121. return res;
  122. }
  123. int getval(int k) //第k个值
  124. {
  125. int x = root;
  126. while(true)
  127. {
  128. if(k <= tr[ls(x)].siz) x = ls(x);
  129. else if(k <= tr[ls(x)].siz + tr[x].cnt) break;
  130. else k -= tr[ls(x)].siz + tr[x].cnt,x = rs(x);
  131. }
  132. splay(x,0);
  133. return tr[x].v;
  134. }
  135. int main()
  136. {
  137. insert(-INF);//加入哨兵
  138. insert(INF);
  139. int n,op,x;
  140. scanf("%d",&n);
  141. while(n--)
  142. {
  143. scanf("%d%d",&op,&x);
  144. if(op == 1){
  145. insert(x);
  146. }
  147. else if(op == 2){
  148. del(x);
  149. }
  150. else if(op == 3)
  151. {
  152. printf("%d\n",getrank(x));
  153. }else if(op == 4){
  154. printf("%d\n",getval(x+1)); //因为有哨兵
  155. }else if(op == 5)
  156. {
  157. printf("%d\n",tr[getpre(x)].v);
  158. }else{
  159. printf("%d\n",tr[getsuc(x)].v);
  160. }
  161. }
  162. return 0;
  163. }

时间复杂度为:O(n*logn)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/334400
推荐阅读
相关标签
  

闽ICP备14008679号