最近数据结构刚好看到了伸展树,在想这个东西有什么应用,于是顺便学习一下。

二叉查找树(BST),对于树上的任意一个节点,节点的左子树上的关键字都小于这个节点的关键字,节点的右子树上的关键字都大于这个节点的关键字。

对二叉查找树进行中序遍历,可以得到一个有序的序列。

下面这些操作的期望复杂度是$O(log N)$,但是如果BST中的数据是有序的序列BST就会变成一条链,复杂度会退化成$O(N)$

为了避免越界减少边界情况的特殊判断,一般在BST中额外插入一个关键码为正无穷和一个关键码为负无穷的节点。

  1. struct BST {
  2. int l, r;
  3. int val;
  4. }a[SIZE];
  5. int tot, root, INF = << ;
  6.  
  7. int NEW(int val)
  8. {
  9. a[++tot].val = val;
  10. return tot;
  11. }
  12.  
  13. void build()
  14. {
  15. NEW(-INF), NEW(INF);
  16. root = ;
  17. a[].r = ;
  18. }

检索时,如果当前节点p的关键字等于val,则已经找到。

如果p的关键字大于val,如果p的左子节点为空说明val不存在,否则在p的左子树中递归进行检索。

如果p的关键字小于val,如果p的右子节点为空说明val不存在,否则在p的右子树中递归进行检索。

  1. int Get(int p, int val)
  2. {
  3. if(p == )return ;
  4. if(val == a[p].val)return p;
  5. return val < a[p].val ? Get(a[p].l, val) : Get(a[p].r, val);
  6. }

插入时,先执行检索操作,知道发现走向的p的子节点为空说明val不存在时,直接建立新节点。

  1. void Insert(int &p, int val)
  2. {
  3. if(p == ){
  4. p = New(val);
  5. return;
  6. }
  7. if(val == a[p].val) return;
  8. if(val < a[p].val) Insert(a[p].l, val);
  9. else Insert(a[p].r, val);
  10. }

val的后继指的是在BST中关键码大于val的前提下,关键码最小的节点。

求后继的过程:初始化ans为具有正无穷关键码的那个节点的编号,然后在BST中检索val。检索过程中,每经过一个点,看看能不能更新ans

当检索完成后,可能没有找到val,此时ans就是答案。

也有可能找到了关键字是val的节点p,但是p没有右子树,那么ans也就是答案。

也有可能是p有右子树,那么说明val的后继不是在刚刚已经经过的那些节点中,所以还要从p的右子节点出发,一直往左走。

  1. nt GetNext(int val)
  2. {
  3. int ans = ;
  4. int p = root;
  5. while(p){
  6. if(val == a[p].val){
  7. if(a[p].r > ){
  8. p = a[p].r;
  9. while(a[p].l > ) p = a[p].l;
  10. ans = p;
  11. }
  12. break;
  13. }
  14. if(a[p].val > val && a[p].val < a[ans].val)ans = p;
  15. p = val < a[p].val ? a[p].l : a[p].r;
  16. }
  17. return ans;
  18. }

删除节点时,也需要先检索val得到节点p。

如果p的孩子只有一个,那么可以直接删除,让p的子节点代替p。

如果p的孩子有两个,就需要在BST中找到val的后继节点nxt。

因为nxt没有左子树,所以可以直接让nxt的右子树代替nxt,然后让nxt代替p。

  1. void remove(int val)
  2. {
  3. int &p = root;
  4. while(p){
  5. if(val == a[p].val) break;
  6. p = val < a[p].val ? a[p].l : a[p].r;
  7. }
  8. if(p == )return ;
  9. if(a[p].l == ){
  10. p = a[p].r;
  11. }
  12. else if(a[p].r == ){
  13. p = a[p].l;
  14. }
  15. else{
  16. int nxt = a[p].r;
  17. while(a[nxt].l > )nxt = a[nxt].l;
  18. remove(a[nxt].val);
  19. a[nxt].l = a[p].l, a[nxt].r = a[p].r;
  20. p = nxt;
  21. }
  22. }

AVL树,是带有平衡条件的二叉查找树。

每个节点的左子树和右子树的高度最多差1。这样就可以使整棵树的深度维持在$O(log N)$

要维持平衡的条件,主要改变的是插入时的操作。

当我们插入了一个节之后,某一条路径上的节点有可能平衡条件被破坏,这时候我们就需要进行旋转操作使他们重新达到平衡条件。

插入时,沿着节点到根更新平衡信息,找到第一个平衡被破坏了的节点(最深的一个)a。

a的两棵子树的高度差2,如果是对a的左儿子的左子树或a的右儿子的右子树进行插入,那么只用进行一次单旋转。

比如这样:

如果是对a的左儿子的右子树或是a的右儿子的左子树进行插入,需要进行一次双旋转。而实际上就是先将k1与k2进行一次旋转,再与k3旋转。

右旋就是把k1变成k2的父节点,k2作为k1的右子节点。zig(p)可以理解成把p的左子节点绕着p向右旋转。

  1. void zig(int &p)
  2. {
  3. int q = a[p].l;
  4. a[p].l = a[q].r, a[q].r = p;
  5. p = q;
  6. }

左旋zag(p)可以理解成把p的右子节点绕着p向左旋转。

  1. void zag(int &p)
  2. {
  3. int q = a[p].r;
  4. a[p].r = a[q].l, a[q].l = p;
  5. p = q;
  6. }

删除操作时,由于支持旋转,我们可以直接找到需要删除的节点,把他旋转成叶节点后直接删除。

伸展树(spaly tree),保证从空树开始任意连续M次对树的操作最多花费$O(M log N)$时间,但是并不排除任意一次操作花费$O(N)$时间的可能。

当一个节点被访问,就将他移动到根上。称为Splay操作。

Spaly操作:令X是在访问路径上的一个(非根)节点,如果X的父节点是树根,就只需要旋转X和树根。

否则分两种情况。

举个书上的习题作为例子。

在实际应用中,我们可以用伸展树维护一些区间的操作。

比如我们要提取区间[a,b],那么我们将a前面一个数对应的结点转到树根,将b 后面一个结点对应的结点转到树根的右边,那么根右边的左子树就对应了区间[a,b]。

与线段树相比,伸展树功能更强大,它能解决以下两个线段树不能解决的问题:

(1) 在a后面插入一些数。方法是:首先利用要插入的数构造一棵伸展树,接着,将a 转到根,并将a 后面一个数对应的结点转到根结点的右边,最后将这棵新的子树挂到根右子结点的左子结点上。

(2)  删除区间[a,b]内的数。首先提取[a,b]区间,直接删除即可。

关于伸展树的实现代码可以参考kuangbin博客中的转载

CH上有一道模板例题

http://contest-hunter.org:83/contest/0x40「数据结构进阶」例题/4601%20普通平衡树

要求实现一下六种操作:

1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

因为给的数可能会重复,而删除时只能删除一个,所以用cnt来记录这个值出现了的次数。

还要求查询排名,所以给节点增加一个size属性,记录以该节点为根的子树中所有节点的cnt之和。

在插入、删除和旋转时从下往上更新size信息。

  1. //#include<bits/stdc++>
  2. #include<stdio.h>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cstring>
  6. #include<stdlib.h>
  7. #include<queue>
  8. #include<map>
  9. #include<stack>
  10. #include<set>
  11.  
  12. #define LL long long
  13. #define ull unsigned long long
  14. #define inf 0x3f3f3f3f
  15.  
  16. using namespace std;
  17.  
  18. const int SIZE = 1e5 + ;
  19. struct Treap{
  20. int l, r;
  21. int val, dat;
  22. int cnt, size;
  23. }a[SIZE];
  24. int tot, root, n, INF = 0x7fffffff;
  25.  
  26. int New(int val)
  27. {
  28. a[++tot].val = val;
  29. a[tot].dat = rand();
  30. a[tot].cnt = a[tot].size = ;
  31. return tot;
  32. }
  33.  
  34. void Update(int p)
  35. {
  36. a[p].size = a[a[p].l].size + a[a[p].r].size + a[p].cnt;
  37. }
  38.  
  39. void build()
  40. {
  41. New(-INF), New(INF);
  42. root = , a[].r = ;
  43. Update(root);
  44. }
  45.  
  46. int GetRankByVal(int p, int val)
  47. {
  48. if(p == )return ;
  49. if(val == a[p].val) return a[a[p].l].size + ;
  50. if(val < a[p].val)return GetRankByVal(a[p].l, val);
  51. return GetRankByVal(a[p].r, val) + a[a[p].l].size + a[p].cnt;
  52. }
  53.  
  54. int GetValByRank(int p, int rank)
  55. {
  56. if(p == )return INF;
  57. if(a[a[p].l].size >= rank)return GetValByRank(a[p].l, rank);
  58. if(a[a[p].l].size + a[p].cnt >= rank)return a[p].val;
  59. return GetValByRank(a[p].r, rank - a[a[p].l].size - a[p].cnt);
  60. }
  61.  
  62. void zig(int &p)
  63. {
  64. int q = a[p].l;
  65. a[p].l = a[q].r;
  66. a[q].r = p;
  67. p = q;
  68. Update(a[p].r);
  69. Update(p);
  70. }
  71.  
  72. void zag(int &p)
  73. {
  74. int q = a[p].r;
  75. a[p].r = a[q].l;
  76. a[q].l = p;
  77. p = q;
  78. Update(a[p].l);
  79. Update(p);
  80. }
  81.  
  82. void Insert(int &p, int val)
  83. {
  84. if(p == ){
  85. p = New(val);
  86. return;
  87. }
  88. if(val == a[p].val){
  89. a[p].cnt++;
  90. Update(p);
  91. return;
  92. }
  93. if(val < a[p].val){
  94. Insert(a[p].l, val);
  95. if(a[p].dat < a[a[p].l].dat)zig(p);//不满足堆性质,右旋
  96. }
  97. else{
  98. Insert(a[p].r, val);
  99. if(a[p].dat < a[a[p].r].dat)zag(p);//不满足堆性质,左旋
  100. }
  101. Update(p);
  102. }
  103.  
  104. int GetPre(int val)
  105. {
  106. int ans = ;
  107. int p = root;
  108. while(p){
  109. if(val == a[p].val){
  110. if(a[p].l > ){
  111. p = a[p].l;
  112. while(a[p].r > )p = a[p].r;
  113. ans = p;
  114. }
  115. break;
  116. }
  117. if(a[p].val < val && a[p].val > a[ans].val) ans = p;
  118. p = val < a[p].val?a[p].l : a[p].r;
  119. }
  120. return a[ans].val;
  121. }
  122.  
  123. int GetNext(int val)
  124. {
  125. int ans = ;
  126. int p = root;
  127. while(p){
  128. if(val == a[p].val){
  129. if(a[p].r > ){
  130. p = a[p].r;
  131. while(a[p].l > )p = a[p].l;
  132. ans = p;
  133. }
  134. break;
  135. }
  136. if(a[p].val > val && a[p].val < a[ans].val )ans = p;
  137. p = val < a[p].val ? a[p].l : a[p].r;
  138. }
  139. return a[ans].val;
  140. }
  141.  
  142. void Remove(int &p, int val)
  143. {
  144. if(p == )return;
  145. if(val == a[p].val){
  146. if(a[p].cnt > ){
  147. a[p].cnt--;
  148. Update(p);
  149. return;
  150. }
  151. if(a[p].l || a[p].r){
  152. if(a[p].r == || a[a[p].l].dat > a[a[p].r].dat){
  153. zig(p);
  154. Remove(a[p].r, val);
  155. }
  156. else {
  157. zag(p);
  158. Remove(a[p].l, val);
  159. }
  160. Update(p);
  161. }
  162. else p = ;
  163. return;
  164. }
  165. val < a[p].val ? Remove(a[p].l, val) : Remove(a[p].r, val);
  166. Update(p);
  167. }
  168.  
  169. int main()
  170. {
  171. build();
  172. cin>>n;
  173. while(n--){
  174. int opt, x;
  175. scanf("%d%d", &opt, &x);
  176. switch(opt){
  177. case :
  178. Insert(root, x);
  179. break;
  180. case :
  181. Remove(root, x);
  182. break;
  183. case :
  184. printf("%d\n", GetRankByVal(root, x) - );
  185. break;
  186. case :
  187. printf("%d\n", GetValByRank(root, x + ));
  188. break;
  189. case :
  190. printf("%d\n", GetPre(x));
  191. break;
  192. case :
  193. printf("%d\n", GetNext(x));
  194. break;
  195. }
  196. }
  197. return ;
  198. }

二叉查找树,AVL树,伸展树【CH4601普通平衡树】的更多相关文章

  1. AVL树、splay树(伸展树)和红黑树比较

    AVL树.splay树(伸展树)和红黑树比较 一.AVL树: 优点:查找.插入和删除,最坏复杂度均为O(logN).实现操作简单 如过是随机插入或者删除,其理论上可以得到O(logN)的复杂度,但是实 ...

  2. 树-伸展树(Splay Tree)

    伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二 ...

  3. 二叉树、红黑树、伸展树、B树、B+树

    好多树啊,程序猿砍树记,吼吼. 许多程序要解决的关键问题是:快速定位特定排序项的能力. 第一类:散列 第二类:字符串查找 第三类:树算法 树算法可以在辅助存储器中存储大量的数据. 二叉树.红黑树和伸展 ...

  4. 伸展树(Splay tree)的基本操作与应用

    伸展树的基本操作与应用 [伸展树的基本操作] 伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性.即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中 ...

  5. [转] Splay Tree(伸展树)

    好久没写过了,比赛的时候就调了一个小时,差点悲剧,重新复习一下,觉得这个写的很不错.转自:here Splay Tree(伸展树) 二叉查找树(Binary Search Tree)能够支持多种动态集 ...

  6. [SinGuLaRiTy] SplayTree 伸展树

    [SinGuLaRiTy-1010]Copyrights (c) SinGuLaRiTy 2017. All Rights Reserved. Some Method Are Reprinted Fr ...

  7. 二叉树总结(五)伸展树、B-树和B+树

    一.伸展树 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作. 因为,它是一颗二叉排序树,所以,它拥有二叉查找树的性质:除此之外,伸展树还具有的一个特点 ...

  8. Splay(区间翻转)&树套树(Splay+线段树,90分)

    study from: https://tiger0132.blog.luogu.org/slay-notes P3369 [模板]普通平衡树 #include <cstdio> #inc ...

  9. 数据结构图解(递归,二分,AVL,红黑树,伸展树,哈希表,字典树,B树,B+树)

    递归反转 二分查找 AVL树 AVL简单的理解,如图所示,底部节点为1,不断往上到根节点,数字不断累加. 观察每个节点数字,随意选个节点A,会发现A节点的左子树节点或右子树节点末尾,数到A节点距离之差 ...

随机推荐

  1. go在ubuntu下安装

    http://blog.csdn.net/Ceciiiilia/article/details/71483221 综合目前网站上的各种安装方法,找到如下简单配置(不会报错或者少报错…) (一)从官网安 ...

  2. JS获取当前日期、比较日期大小

    //获取当前时间,格式YYYY-MM-DD function getNowFormatDate() { var date = new Date(); var seperator1 = "-& ...

  3. POSIX 线程的创建与退出

    前言 创建线程: pthread_create() 退出线程: pthread_exit()return pthread_cancel() 线程的创建 使用多线程,首先就需要创建一个新线程.那么线程是 ...

  4. 一个会学习(观察->活学->求变)的人,在任何领域都能变得强大无比

      开始今天的话题之前,我说个小故事.   很早以前有一部美剧,叫<Hero>.   大概讲的是正反两派都是一群有超能力的人,彼此为了某个巨大的阴谋互相撕逼了十多集.虽然剧情很老套,但是让 ...

  5. c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习

    c#中@标志的作用   参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...

  6. Effective Java 第三版——62. 当有其他更合适的类型时就不用字符串

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  7. 单表40G,不要吃惊,还能往里面插入数据,哈哈

    单表40G,不要吃惊,还能往里面插入数据,哈哈 上图:

  8. idea java.lang.OutOfMemoryError: PermGen space

    测试修改 idea  vm.options 无效 , 跟eclipse类似 成功如下 -Xms800m -Xmx800m -XX:MaxNewSize=256m -XX:MaxPermSize=256 ...

  9. ssh远程登录不上的处理

    最近ssh远程主机突然登录不上,提示如下: 后来咨询了一下云主机的客服,估计我们的主机时多次尝试密码错误被系统屏蔽IP了.于是问了一下同事,确实有同事最近密码错误多次尝试的问题. 于是按照客服给的方法 ...

  10. 在BootStrap的modal中使用Select2

      bootstrap 3 $.fn.modal.Constructor.prototype.enforceFocus = function() {}; bootstrap4 $.fn.modal.C ...