[## 一、什么是SBT

SBT , 即 Size Balanced Tree,是一种平衡二叉排序树,根据其节点中 Size 域大小进行平衡,由陈启峰于2006年在冬令营提出

二、为什么写SBT:

  1. 书写简单:在常用的平衡树( Spaly , Treap , AVL , Red-Black Tree ……)中,SBT的码量较小,只是在原有 BST 的基础上加入 Maintain 函数以及对Size域的一些调整.

  2. 理解简单:SBT 的基本思想相对于别的平衡树( Splay , AVL ,RBT……)来说更加容易被初学者所接受

  3. 效率较高:其核心算法 Maintain 的复杂度平摊为O(1)(不会证),(证明过程可见论文),且其趋近于一棵完美的二叉排序树。

三、平衡性质

SBT总满足:

1.Size[Right[t]] >= Size[Left[Left[t]]],Size[Right[Left[t]]]
2.Size[Left[t]] >= Size[Left[Right[t]]],Size[Right[Right[t]]]

翻译过来即:
一个节点的左右儿子的子树大小总不小于其兄弟的儿子(侄子)的子树大小

而这种平衡性质便是以 Maintain 来维持

四、基本操作

0、声明:

int n, root, tot;
int Right[100100], Left[100100], Size[100100], Val[100100];

1、旋转

实现 Maintain 的基本操作即 左旋 与 右旋

左旋即将操作点(L)变为其先前右儿子(P)的左儿子,先前右儿子(P)变为操作点(L)的父亲,同时保持BST性质。(如图,先前P为L的右儿子,现在L为P的左儿子)

void Left_rotate(int &p)//
{int k = Right[p];Right[p] = Left[k];//将p的右儿子变为其原本右儿子的左儿子Left[k] = p;//将p原本的右儿子的左儿子变为pSize[k] = Size[p];//k取代p的Size值Size[p] = Size[Right[p]] + Size[Left[p]] + 1;//计算p的大小值p = k;
}

右旋即将操作点(P)变为其先前左儿子(L)的右儿子,先前左儿子(L)变为操作点(P)的父亲,同时保持BST性质。(如图,先前L为P的左儿子,现在P为L的右儿子)

void Right_rotate(int &p)//右旋与左旋相反
{int k = Left[p];Left[p] = Right[k];Right[k] = p;Size[k] = Size[p];Size[p] = Size[Right[p]] + Size[Left[p]] + 1;p = k;
}

2、Maintain

在什么情况下会使用 Maintain 呢?就是在性质1或2不满足,平衡树不平衡。所以,当一棵 SBT 变化的时候会使用 Maintain。
在所有的基本操作中,改变 BST 的也就两个,Insert 和 Delete。而 Delete 是不改变树的形态的。

虽然它(Delete)会破坏SBT的结构,但因为使用 Insert 操作,它还是一棵高度为O(log2 K)的 SBT 。这里的 K 是所有插入节点的个数,而不是当前节点的个数

而插入一个节点后中,我们需要对插入过程中经过的点进行 Maintain 操作,否则以这些点为根的子树有可能不平衡。
Maintain(p)指的是调整以 p 为根的 SBT 。

我们可以发现,性质1,2是基本对称的,所以我们可以先讨论性质1,再类比性质2
性质1.a: 如图,Size[Right[t]] < Size[Left[Left[t]]],

(1)为了配平,我们可以先将L进行右旋操作,变为下图;

(2)如图,有可能Size[C] or Size[D] 大于 Size[B] ,所以需要再次 Maintain(right[p])(p在上一次操作中指向了L);

(3)调整完后,我们会发现以L为根节点的子树仍有可能不平,所以需要继续执行Maintain(p);

性质1.b: 如图,Size[Right[t]] < Size[Right[Left[t]]],

(1)先调整以L为根节点的子树,对L执行左旋操作,变为下图

(2)再对P执行右旋操作,变为下图。

(3)我们可以发现,虽然这棵树炒鸡不平衡,但子树 A,E,F,R只是挪了挪位置,其平衡性质并没有改变,所以我们只需要再Maintain(L),Maintain(P),Maintain(B)即可。

性质2.a:Size[Left[t]] < Size[Right[Right[t]]]:
与性质1.a刚好相反,可以自行画图理解。

性质2.b:Size[Left[t]] < Size[Left[Right[t]]]:
与性质1.b刚好相反,可以自行画图理解。
其实是我不想画图了

综上,我们可以大致的写出如下程序:

void Maintian(int &p)
{if (Size[Left[Left[p]]] > Size[Right[p]]){Right_rotate(p);Maintian(Right[p]);Maintian(p);return;}if (Size[Right[Left[p]]] > Size[Right[p]]){Left_rotate(Left[p]);Right_rotate(p);Maintian(Left[p]);Maintian(Right[p]);Maintian(p);return;}if (Size[Left[Left[p]]] > Size[Right[p]]){Left_rotate(p);Maintian(Left[p]);Maintian(p);return;}if (Size[Right[Left[p]]] > Size[Right[p]]){Right_rotate(Right[p]);Left_rotate(p);Maintian(Left[p]);Maintian(Right[p]);Maintian(p);return;}return;
}

不过我们还可以有另一种写法,即用一个bool参数flag来表示其究竟是违反了性质1还是性质2,并且将一些重合的操作写在外面

void Maintian(int &p, bool flag)
{if (!flag){if (Size[Left[Left[p]]] > Size[Right[p]])Right_rotate(p);else if (Size[Right[Left[p]]] > Size[Right[p]]){Left_rotate(Left[p]);Right_rotate(p);}elsereturn;}else{if (Size[Right[Right[p]]] > Size[Left[p]])Left_rotate(p);else if (Size[Left[Right[p]]] > Size[Left[p]]){Right_rotate(Right[p]);Left_rotate(p);}elsereturn;}Maintian(Left[p], false);//以Left[p]为根的子树中,左子树定比右子树的儿子子树的大小要大Maintian(Right[p], true);//以Right[p]为根的子树中,右子树定比左子树的儿子子树的大小要大Maintian(p, true);Maintian(p, false);
}

至于 Maintain 的效率云云,可见陈启峰大佬的论文:中文译文版 英文版

3、Insert

与 BST 差不多,只是多加了 Maintain 与 对Size的处理,故直接放代码:

void Insert(int &p, int &v)
{if (!p){p = ++tot;Val[++p] = v;Size[p] = 1;//Sizereturn;}Size[p]++;//Sizeif (v < Val[p])Insert(Left[p], v);elseInsert(Right[p], v);Maintian(p, v >= Val[p]);//Maintain
}

4、Delete
与 BST 差不多,只需再注意对 Size 的处理,直接放代码:

int Delete(int &p, int x)
{Size[p]--;int tmp;if (x == Val[p] || (x < Val[p] && !Left[p]) || (x > Val[p] && !Right[p])){tmp = Val[p];if (!Left[p] || !Right[p])p = Left[p] + Right[p];elseVal[p] = erase(Left[p], Val[p] + 1);return tmp;}if (x < Val[p])tmp = erase(Left[p], x);elsetmp = erase(Right[p], x);return tmp;
}

5、Rank:

查找一个值在树中的大小排名,细节在代码中

int Rank(int &p, int &v)
{if (p == 0)return 1;if (v <= Val[p])return Rank(Left[p], v);elsereturn Size[Left[p]] + 1 + Rank(Right[p], v);
}

6、Select:

查找一个排名在树中的具体值,细节在代码中:

int Select(int &p, int k)
{if (Size[Left[p]] + 1 == k)return Val[p];if (k <= Size[Left[p]])return Select(Left[p], k);elsereturn Select(Right[p], k - 1 - Size[Left[p]]);
}

7、Find:

查找一个值是否存在

bool Find(int &p, int &v)
{if (p == 0)return 0;if (v < Val[p])return Find(Left[p], v);else if (v == Val[p])return 1;elsereturn Find(Right[p], v);
}

8、找前驱/后继:

细节在代码中:

int GetLast(int &p, int &v)//前驱
{if (p == 0)return v;int x;if (v <= Val[p])return GetLast(Left[p], v);else{x = GetLast(Right[p], v);if (x == v)x = Val[p];return x;}
}
int GetNext(int &p, int &v)//后继
{if (p == 0)return v;int x;if (v >= Val[p])return GetNext(Right[p], v);else{x = GetNext(Left[p], v);if (x == v)x = Val[p];return x;}
}

9、完整代码:

#include <bits/stdc++.h>
using namespace std;
int n, root, tot;
int Right[100100], Left[100100], Size[100100], Val[100100];
void Right_rotate(int &p)
{int k = Left[p];Left[p] = Right[k];Right[k] = p;Size[k] = Size[p];Size[p] = Size[Right[p]] + Size[Left[p]] + 1;p = k;
}
void Left_rotate(int &p)
{int k = Right[p];Right[p] = Left[k];Left[k] = p;Size[k] = Size[p];Size[p] = Size[Right[p]] + Size[Left[p]] + 1;p = k;
}
void Maintian(int &p, bool flag)
{if (!flag){if (Size[Left[Left[p]]] > Size[Right[p]])Right_rotate(p);else if (Size[Right[Left[p]]] > Size[Right[p]]){Left_rotate(Left[p]);Right_rotate(p);}elsereturn;}else{if (Size[Right[Right[p]]] > Size[Left[p]])Left_rotate(p);else if (Size[Left[Right[p]]] > Size[Left[p]]){Right_rotate(Right[p]);Left_rotate(p);}elsereturn;}Maintian(Left[p], false);Maintian(Right[p], true);Maintian(p, true);Maintian(p, false);
}
void Insert(int &p, int &v)
{if (!p){p = ++tot;Val[++p] = v;Size[p] = 1;return;}Size[p]++;if (v < Val[p])Insert(Left[p], v);elseInsert(Right[p], v);Maintian(p, v >= Val[p]);
}
int Delete(int &p, int x)
{Size[p]--;int tmp;if (x == Val[p] || (x < Val[p] && !Left[p]) || (x > Val[p] && !Right[p])){tmp = Val[p];if (!Left[p] || !Right[p])p = Left[p] + Right[p];elseVal[p] = erase(Left[p], Val[p] + 1);return tmp;}if (x < Val[p])tmp = erase(Left[p], x);elsetmp = erase(Right[p], x);return tmp;
}
int Rank(int &p, int &v)
{if (p == 0)return 1;if (v <= Val[p])return Rank(Left[p], v);elsereturn Size[Left[p]] + 1 + Rank(Right[p], v);
}
int Select(int &p, int k)
{if (Size[Left[p]] + 1 == k)return Val[p];if (k <= Size[Left[p]])return Select(Left[p], k);elsereturn Select(Right[p], k - 1 - Size[Left[p]]);
}
int GetLast(int &p, int &v)
{if (p == 0)return v;int x;if (v <= Val[p])return GetLast(Left[p], v);else{x = GetLast(Right[p], v);if (x == v)x = Val[p];return x;}
}
int GetNext(int &p, int &v)
{if (p == 0)return v;int x;if (v >= Val[p])return GetNext(Right[p], v);else{x = GetNext(Left[p], v);if (x == v)x = Val[p];return x;}
}
void LDR(int &p)
{if (p == 0)return;LDR(Left[p]);printf("%d ", Val[p]);LDR(Right[p]);return;
}
bool Find(int &p, int &v)
{if (p == 0)return 0;if (v < Val[p])return Find(Left[p], v);else if (v == Val[p])return 1;elsereturn Find(Right[p], v);
}
int Max(int &p)
{if (Right[p] != 0)return Max(Right[p]);elsereturn Val[p];
}
int Min(int &p)
{if (Left[p] != 0)return Min(Left[p]);elsereturn Val[p];
}int main()
{scanf("%d", &n);int k, v;for (int i = 1; i <= n; i++){scanf("%d%d", &k, &v);if (k == 1)Insert(root, v);else if (k == 2)Delete(root, v);else if (k == 3)printf("%d\n", Rank(root, v));else if (k == 4)printf("%d\n", Select(root, v));else if (k == 5)printf("%d\n", GetLast(root, v));else if (k == 6)printf("%d\n", GetNext(root, v));else if (k == 7)LDR(root);}return 0;
}

四、推荐习题:

1 【模板】普通平衡树
2、郁闷的出纳员

五、关于 SBT 的几个疑惑暂时只有一个

1、为啥没多少人用SBT???

六、参考文献:

1、感谢陈启峰大佬的论文(虽然我并没有找到原版)
2、感谢 I_AM_HelloWord,olinr 的博客

转载于:https://www.cnblogs.com/fpjo/p/10816808.html

【学习笔记】SBT学习笔记相关推荐

  1. 高效学习方法论的学习笔记

    Author:鲁力(地山) 0.前言与致谢 如何提高学习效率?这一问题长期以来都是网上讨论的热点话题,就这一问题网上很多大咖以书籍.博客.知乎回答.视频等不同形式都发表过自己的观点见解和方法论.笔者在 ...

  2. Yann Lecun纽约大学《深度学习》2020课程笔记中文版,干货满满!

    关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! [导读]Yann Lecun在纽约大学开设的2020春季<深度学习>课 ...

  3. 【干货】机器学习经典书PRML 最新 Python 3 代码实现,附最全 PRML 笔记视频学习资料...

    关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 将 Bishop 大神的 PRML 称为机器学习圣经一点也不为过,该书系统地介绍了 ...

  4. 系列笔记 | 深度学习连载(6):卷积神经网络基础

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 卷积神经网络其实早在80年代,就被神经网络泰斗Lecun 提出[LeNet-5, LeCun ...

  5. 系列笔记 | 深度学习连载(5):优化技巧(下)

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 深度学习中我们总结出 5 大技巧: 本节继续从第三个开始讲起. 3. Early stoppi ...

  6. 系列笔记 | 深度学习连载(4):优化技巧(上)

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 深度学习中我们总结出 5 大技巧: 1. Adaptive Learning Rate 我们先 ...

  7. 系列笔记 | 深度学习连载(2):梯度下降

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 我们回忆深度学习"三板斧": 1. 选择神经网络 2. 定义神经网络的好坏 ...

  8. React学习:入门实例-学习笔记

    文章目录 React学习:入门实例-学习笔记 React的核心 需要引入三个库 什么是JSX react渲染三步骤 React学习:入门实例-学习笔记 React的核心 1.组件化:把整一个网页的拆分 ...

  9. cocos2d学习笔记2——学习资源

    1. 视频 找了好几个视频,有一些讲得好的文件资源没有,后来终于找到一个讲得不错还有文件资源的,还有高清下载地址,虽然是2.2版本的,但是确实能学到不少东西,对用cocos2d做游戏有了基本的印象,对 ...

  10. 使用html记笔记,开始学习HTML,并记下笔记

    开始学习HTML,并记下笔记. 外边距(不影响可见框大小,影像盒子位置) margin-top(上) right(右) bottom(下) left(左) "外边距也可以为一个负值,元素会反 ...

最新文章

  1. oracle照片字节大小值,Oracle每条记录的平均字节数
  2. mysql基础sql语句_SQL基础语句汇总
  3. 多功能的图像超分辨模型:用于盲图像超分辨的非对称卷积神经网络
  4. vue1和vue2获取dom元素的方法 及 nextTick() 、$nextTick()
  5. MapXtreme 使用技巧10例
  6. Android BLE蓝牙详细解读
  7. 配置eclipse编写html/js/css/jsp/java时自动提示
  8. 提取HTML代码中的网址
  9. Head First SQL Your Brain on SQL读书笔记
  10. 法兰克机械手手动操作_学习FANUC机器人编程设定,必懂这2个技巧!
  11. FIR 滤波器参数意义
  12. LDC注册数据获取|ACE2004, ACE 2005,OntoNotes等数据
  13. 婴儿监护物联网系统设计系统源码开放
  14. 软件测试——三角形问题测试用例练习
  15. U盘文件找不到了?莫慌!!!
  16. 通用计算机的发展历程是巨型机大型机小型机,计组1——计算机系统概述
  17. oppor829t如何刷机_oppo R829t (R1 移动版)一键救砖教程,轻松刷回官方系统
  18. python输出中文加数字_Python实现阿拉伯数字加上中文数字
  19. Groovy~Groovy的List(列表)操作
  20. 20190121——不羡神仙 Java设计模式

热门文章

  1. 深度学习+语音,基础普及篇笔记(一)
  2. Review | 科学禁食时代的来临
  3. 深度挖掘新闻营销带给企业的好处和优势
  4. 本地测试dubbo远程调用找不到provider
  5. 机器学习(浙大胡浩基)
  6. 12306 python登录并保存cookie
  7. 微信小程序口袋奇兵选服务器,口袋奇兵小程序
  8. 思科模拟器 --- 路由器静态路由配置
  9. python之首字母大写
  10. wordpress主题免费- wordpress插件以及主题下载