【学习笔记】SBT学习笔记
[## 一、什么是SBT
SBT , 即 Size Balanced Tree,是一种平衡二叉排序树,根据其节点中 Size 域大小进行平衡,由陈启峰于2006年在冬令营提出
二、为什么写SBT:
书写简单:在常用的平衡树( Spaly , Treap , AVL , Red-Black Tree ……)中,SBT的码量较小,只是在原有 BST 的基础上加入 Maintain 函数以及对Size域的一些调整.
理解简单:SBT 的基本思想相对于别的平衡树( Splay , AVL ,RBT……)来说更加容易被初学者所接受
效率较高:其核心算法 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学习笔记相关推荐
- 高效学习方法论的学习笔记
Author:鲁力(地山) 0.前言与致谢 如何提高学习效率?这一问题长期以来都是网上讨论的热点话题,就这一问题网上很多大咖以书籍.博客.知乎回答.视频等不同形式都发表过自己的观点见解和方法论.笔者在 ...
- Yann Lecun纽约大学《深度学习》2020课程笔记中文版,干货满满!
关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! [导读]Yann Lecun在纽约大学开设的2020春季<深度学习>课 ...
- 【干货】机器学习经典书PRML 最新 Python 3 代码实现,附最全 PRML 笔记视频学习资料...
关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 将 Bishop 大神的 PRML 称为机器学习圣经一点也不为过,该书系统地介绍了 ...
- 系列笔记 | 深度学习连载(6):卷积神经网络基础
点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 卷积神经网络其实早在80年代,就被神经网络泰斗Lecun 提出[LeNet-5, LeCun ...
- 系列笔记 | 深度学习连载(5):优化技巧(下)
点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 深度学习中我们总结出 5 大技巧: 本节继续从第三个开始讲起. 3. Early stoppi ...
- 系列笔记 | 深度学习连载(4):优化技巧(上)
点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 深度学习中我们总结出 5 大技巧: 1. Adaptive Learning Rate 我们先 ...
- 系列笔记 | 深度学习连载(2):梯度下降
点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 我们回忆深度学习"三板斧": 1. 选择神经网络 2. 定义神经网络的好坏 ...
- React学习:入门实例-学习笔记
文章目录 React学习:入门实例-学习笔记 React的核心 需要引入三个库 什么是JSX react渲染三步骤 React学习:入门实例-学习笔记 React的核心 1.组件化:把整一个网页的拆分 ...
- cocos2d学习笔记2——学习资源
1. 视频 找了好几个视频,有一些讲得好的文件资源没有,后来终于找到一个讲得不错还有文件资源的,还有高清下载地址,虽然是2.2版本的,但是确实能学到不少东西,对用cocos2d做游戏有了基本的印象,对 ...
- 使用html记笔记,开始学习HTML,并记下笔记
开始学习HTML,并记下笔记. 外边距(不影响可见框大小,影像盒子位置) margin-top(上) right(右) bottom(下) left(左) "外边距也可以为一个负值,元素会反 ...
最新文章
- oracle照片字节大小值,Oracle每条记录的平均字节数
- mysql基础sql语句_SQL基础语句汇总
- 多功能的图像超分辨模型:用于盲图像超分辨的非对称卷积神经网络
- vue1和vue2获取dom元素的方法 及 nextTick() 、$nextTick()
- MapXtreme 使用技巧10例
- Android BLE蓝牙详细解读
- 配置eclipse编写html/js/css/jsp/java时自动提示
- 提取HTML代码中的网址
- Head First SQL Your Brain on SQL读书笔记
- 法兰克机械手手动操作_学习FANUC机器人编程设定,必懂这2个技巧!
- FIR 滤波器参数意义
- LDC注册数据获取|ACE2004, ACE 2005,OntoNotes等数据
- 婴儿监护物联网系统设计系统源码开放
- 软件测试——三角形问题测试用例练习
- U盘文件找不到了?莫慌!!!
- 通用计算机的发展历程是巨型机大型机小型机,计组1——计算机系统概述
- oppor829t如何刷机_oppo R829t (R1 移动版)一键救砖教程,轻松刷回官方系统
- python输出中文加数字_Python实现阿拉伯数字加上中文数字
- Groovy~Groovy的List(列表)操作
- 20190121——不羡神仙 Java设计模式