BST,Splay平衡树学习笔记

1.二叉查找树BST

BST是一种二叉树形结构,其特点就在于:每一个非叶子结点的值都大于他的左子树中的任意一个值,并都小于他的右子树中的任意一个值。

2.BST的用处

如果利用朴素算法序列中的第k大的数,最坏的情况下可能达到O(N*logN),而由于BST的特性,我们可以把复杂度优化为O(logN),不仅如此,我们还可以在O(logn)的复杂度下查找元素,O(1)的复杂度下修改元素。对于有些数据来说,极大地节约了时间。

3.BST的优化---splay平衡树

再优秀的算法都会有自己的缺点,而BST的缺点就在于,在极端情况下,树形结构会退化为一条链,此时进行操作甚至比朴素算法还要劣。那啷个办嘞?肯定要想办法降低树的深度于是天空一声巨响,,神奇的splay,treap闪亮登场qwq。

每一次的询问值x,我们把他移到root的位置,在这样做的同时,我们维护好这个BST的性质(忘了的自己看第一条0_0)。就比如说,我们把这条链调成splay的形式:

是不是感觉优化过后好厉害~咳咳,下面还有更厉害的

优化核心:旋转操作Rotate

定义rotate(now,goal),将节点now旋转至goal节点处,同时,为了维护BST的性质,我们需要将树的结构进行调整:(这里只考虑把now转移到他的父亲fa)

step1:询问now,fa是他们父亲的哪个儿子假设查询结果为l_now,l_fa

step2:把now放到他父亲的位置上,维护节点信息之后我们来处理他原来的爸爸fa

step3:按道理来讲,fa由于比now大(其他情况自己画图我懒得动QAQ),fa本来应该变为now的右儿子(由于BST的性质),可是此时now已经有一个孩子ch了,如果强行接入,ch就没爸爸了,他会很伤心的,为了维护一棵快乐的BST,我们决定再给他找个爸爸,在上面操作中,fa失去了一个儿子(now变为了他的父亲),他现在也是难过的摇头晃脑,所以,我们不妨直接让fa领养ch,由于ch为原树中fa左子树中的结点,并且为now的右儿子,所以有如下关系:

now<ch<fa,刚好,将ch拿给fa当儿子完美的维护了BST的性质,所以,这是一个合法的操作(我们就可以给他发领养证啦:)),最后再维护一下结点信息就好啦QAQ

slpay操作就是不断地上旋直到达到目标,rotate则是单旋

下面上图:

//rotate操作我感觉讲的不是很清楚,这里推荐一下Clove学姐的文章,下面附上链接https://www.cnblogs.com/hua-dong/p/7822815.html

下面来道题练习一下:https://www.luogu.com.cn/problem/P3391

说实话这道题入门有点难,其实这里bst我维护的是输出的优先级,而翻转操作则是交换优先级,所以立刻想到交换中间结点的左右子树,那怎么保证以这个结点为根节点的子树完全且恰好包含题目要翻转的区间呢?

不妨这样考虑:对于每一个要翻转的区间,[l,r],我们把l-1号节点旋转到根节点处,再把r+1旋转到l-1的又孩子处,你们自己按照rotate转一下之后就会发现,我们要维护的区间恰好是根节点的右子树的左子树 'O' (手动惊讶Σ(⊙▽⊙"a)

就okk啦~

下面上代码,有注释滴,码风还好不用担心:

  1. 1 #include<iostream>
  2. 2 #include<cstdio>
  3. 3 #define N 100005
  4. 4
  5. 5 using namespace std;
  6. 6
  7. 7 int ch[N][2],fa[N],size[N],ans[N],val[N],n,m,root,cnt=0;
  8. 8 bool tag[N]={0};
  9. 9
  10. 10 int Read()
  11. 11 {
  12. 12 int num=0,k=1;
  13. 13 char c=getchar();
  14. 14 while(c!='-'&&((c>'9')||(c<'0'))) c=getchar();
  15. 15 if(c=='-')
  16. 16 {
  17. 17 k=-1;
  18. 18 c=getchar();
  19. 19 }
  20. 20 while(c>='0'&&c<='9')
  21. 21 {
  22. 22 num=(num<<3)+(num<<1)+c-'0',c=getchar();
  23. 23 }
  24. 24 return num*k;
  25. 25 }
  26. 26
  27. 27 void push_up(int x)
  28. 28 {
  29. 29 size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
  30. 30 } //以x为root的子树中结点的个数
  31. 31
  32. 32 int locate(int x)
  33. 33 {
  34. 34 return ch[fa[x]][1]==x;
  35. 35 } // 定位x是他爸爸的哪个儿子
  36. 36
  37. 37 void rotate(int x) // 把x旋转到root
  38. 38 {
  39. 39 int y=fa[x],z=fa[y],b=locate(x),c=locate(y),a=ch[x][!b];
  40. 40 if(fa[y]) ch[z][c]=x ; //如果x的爸爸不是root
  41. 41 else
  42. 42 {
  43. 43 root=x;
  44. 44 fa[x]=z; //替代root的位置
  45. 45 }
  46. 46 if(a) fa[a]=y;ch[y][b]=a;//旋转
  47. 47 ch[x][!b]=y;
  48. 48 fa[y]=x;
  49. 49 push_up(y);
  50. 50 push_up(x);
  51. 51 }
  52. 52
  53. 53 int build(int l,int r,int f)//构造splay
  54. 54 {
  55. 55 int mid=(l+r)>>1;
  56. 56 val[mid]=ans[mid];
  57. 57 fa[mid]=f;
  58. 58 if(l<mid) ch[mid][0]=build(l,mid-1,mid);
  59. 59 if(mid<r) ch[mid][1]=build(mid+1,r,mid);
  60. 60 push_up(mid);
  61. 61 return mid;
  62. 62 }
  63. 63
  64. 64 void push_down(int x)
  65. 65 {
  66. 66 if(!tag[x]) return ;//x结点上覆盖了懒标记
  67. 67 tag[ch[x][0]]^=1;
  68. 68 tag[ch[x][1]]^=1;
  69. 69 swap(ch[x][0],ch[x][1]);
  70. 70 tag[x]=false;
  71. 71 }
  72. 72
  73. 73 int query(int x) //询问x在splay中的结点编号
  74. 74 {
  75. 75 int now=root;
  76. 76
  77. 77 while(true)
  78. 78 {
  79. 79 push_down(now);
  80. 80 if(ch[now][0]&&x<=size[ch[now][0]]) now=ch[now][0];//对于splay上值为a的结点有size[ch[now][0]]>=a这里判断是否在左子树
  81. 81 else
  82. 82 {
  83. 83 int num=(ch[now][0] ? size[ch[now][0]] :0) +1;//计算当前结点的编号:左子树根节点编号+1
  84. 84 if(num==x) return now;//
  85. 85 x-=num;
  86. 86 now=ch[now][1];
  87. 87 }
  88. 88 }
  89. 89 }
  90. 90
  91. 91 void splay(int x,int goal)//把now转为goal的子节点
  92. 92 {
  93. 93 while(fa[x]!=goal)
  94. 94 {
  95. 95 int y=fa[x],z=fa[y];
  96. 96 if(z==goal) rotate(x);//单旋
  97. 97 else
  98. 98 {
  99. 99 if(locate(x)==locate(y))
  100. 100 {
  101. 101 rotate(y);
  102. 102 rotate(x);
  103. 103 }
  104. 104 else
  105. 105 {
  106. 106 rotate(x);
  107. 107 rotate(x);
  108. 108 }
  109. 109 }
  110. 110 }
  111. 111 }
  112. 112
  113. 113 void print(int now)
  114. 114 {
  115. 115 push_down(now);
  116. 116 if(ch[now][0]) print(ch[now][0]);
  117. 117
  118. 118 ans[++cnt]=val[now];
  119. 119
  120. 120 if(ch[now][1]) print(ch[now][1]);
  121. 121
  122. 122 }
  123. 123
  124. 124 int main ()
  125. 125 {
  126. 126 int l,r;
  127. 127 n=Read(),m=Read();
  128. 128 for(int i=1;i<=n+2;i++) ans[i]=i-1;// 记录前驱
  129. 129
  130. 130 root=build(1,n+2,0);
  131. 131
  132. 132 for(int i=1;i<=m;i++)
  133. 133 {
  134. 134 l=Read(),r=Read();
  135. 135 int x=query(l),y=query(r+2); //查找x的前驱所在的位置,和y后驱所在的位置,因为预处理时ans存的是前趋,所以直接查找x,而y的后驱变成了y+2
  136. 136 splay(x,0);
  137. 137 splay(y,x); //将x前驱上旋至根节点,y的后驱上旋成根节点右儿子的左子树
  138. 138 tag[ch[ch[root][1]][0]]^=1;//经过旋转后,此时根节点的右儿子的左子树就是需要翻转的区间,所以lazy标记
  139. 139
  140. 140 }
  141. 141
  142. 142 print(root);
  143. 143
  144. 144 for(int i=1;i<=n;i++) printf("%d ",ans[i+1]);
  145. 145 return 0;
  146. 146 }

好啦,BST就到这里啦~第一次写文章,写的不好的地方原谅一下,也欢迎大家提问和指正,拜拜~

ps.转载记得注明出处哦,你看人家那么辛苦滴QAQ

BST,Splay平衡树学习笔记的更多相关文章

  1. 平衡树学习笔记(3)-------Splay

    Splay 上一篇:平衡树学习笔记(2)-------Treap Splay是一个实用而且灵活性很强的平衡树 效率上也比较客观,但是一定要一次性写对 debug可能不是那么容易 Splay作为平衡树, ...

  2. 平衡树学习笔记(6)-------RBT

    RBT 上一篇:平衡树学习笔记(5)-------SBT RBT是...是一棵恐怖的树 有多恐怖? 平衡树中最快的♂ 不到200ms的优势,连权值线段树都无法匹敌 但是,通过大量百度,发现RBT的代码 ...

  3. 平衡树学习笔记(5)-------SBT

    SBT 上一篇:平衡树学习笔记(4)-------替罪羊树 所谓SBT,就是Size Balanced Tree 它的速度很快,完全碾爆Treap,Splay等平衡树,而且代码简洁易懂 尤其是插入节点 ...

  4. 平衡树学习笔记(2)-------Treap

    Treap 上一篇:平衡树学习笔记(1)-------简介 Treap是一个玄学的平衡树 为什么说它玄学呢? 还记得上一节说过每个平衡树都有自己的平衡方式吗? 没错,它平衡的方式是......rand ...

  5. 普通平衡树学习笔记之Splay算法

    前言 今天不容易有一天的自由学习时间,当然要用来"学习".在此记录一下今天学到的最基础的平衡树. 定义 平衡树是二叉搜索树和堆合并构成的数据结构,它是一 棵空树或它的左右两个子树的 ...

  6. [普通平衡树splay]【学习笔记】

    参考: http://blog.csdn.net/clove_unique/article/details/50630280 gty课件 找一个好的风格太难了,自己习惯用struct,就强行用stru ...

  7. 文艺平衡Splay树学习笔记(2)

    本blog会讲一些简单的Splay的应用,包括但不局限于 1. Splay 维护数组下标,支持区间reserve操作,解决区间问题 2. Splay 的启发式合并(按元素多少合并) 3. 线段树+Sp ...

  8. splay tree 学习笔记

    首先感谢litble的精彩讲解,原文博客: litble的小天地 在学完二叉平衡树后,发现这是只是一个不稳定的垃圾玩意,真正实用的应有Treap.AVL.Splay这样的查找树.于是最近刚学了学了点S ...

  9. 浅谈树套树(线段树套平衡树)&学习笔记

    0XFF 前言 *如果本文有不好的地方,请在下方评论区提出,Qiuly感激不尽! 0X1F 这个东西有啥用? 树套树------线段树套平衡树,可以用于解决待修改区间\(K\)大的问题,当然也可以用 ...

随机推荐

  1. House of Orange

    题目附件:https://github.com/ctfs/write-ups-2016/tree/master/hitcon-ctf-2016/pwn/house-of-orange-500 查看程序 ...

  2. Django 多页面间参数传递用session方法(Django七)

    由一个页面跳转至另一个页面可以有render中携带几个参数,如下:照上例便在跳转到homepage页面后使用传递的四个参数了 但问题是如何在由homepage跳转到其他页面时仍可以使用这四个参数呢?我 ...

  3. Linux:less and Aix:more

    在运维工作中,经常要查询应用日志,有Linux和Aix系统,个人感觉,Linux查询日志用less命令比较方便,Aix查询日志用more命令比较方便,在此总结一下两个命令的使用方法 AIX more命 ...

  4. [Binder深入学习一]Binder驱动——基础数据结构

    具体代码路径: kernel/drivers/staging/android/binder.c kernel/drivers/staging/android/binder.h /* * binder_ ...

  5. 移动web开发之rem适配布局

    移动web开发之rem适配布局 方案: 页面布局文字能否随着屏幕大小变化而变化 流式布局和flex布局主要针对于宽度布局,那高度如何布局? 怎样让屏幕发生变化的时候元素高度和宽度等比例缩放? 1. r ...

  6. c语言汇总1

    (1--10) 1.机器语言(0,1) 汇编语言(换元法) 高级语言(人) 2.C语言由函数组成而成 main函数系统会自动启动它 3.main函数格式: int main(){ call(): re ...

  7. 003.当在windows终端输入ipconfig时,显示不是内部或外部命令,也不是可运行的程序或批处理文件

    当在windows终端输入ipconfig时,显示不是内部或外部命令,也不是可运行的程序或批处理文件,这是环境变量的问题: 右键我的电脑→→→属性→→→高级系统设置→→→(高级)环境变量 在弹出的窗口 ...

  8. C++获取运行程序当前目录

    1 HMODULE GetSelfModuleHandle() 2 { 3 MEMORY_BASIC_INFORMATION mbi; 4 return ((::VirtualQuery(GetSel ...

  9. gRPC-go 入门(1):Hello World

    摘要 在这篇文章中,主要是跟你介绍一下gRPC这个东西. 然后,我会创建一个简单的练习项目,作为gRPC的Hello World项目. 在这个项目中,只有很简单的一个RPC函数,用于说明gRPC的工作 ...

  10. 记一次uwsgi django nginx 调优

    [uwsgi] project = fortune_cat uid = ubuntu gid = ubuntu path = fortune_cat base = /home/%(uid) chdir ...