数据结构-树
# 前置定义
满(full)二叉树:
- 英文wiki:每个节点都有0或2个子节点
- 国内教材:除最低层的节点外,每个节点都有2个子节点(即第层的节点数为)
完美(perfect)二叉树:(国内版)满二叉树
完全二叉树:除最底层外,每一层都被填满;最底层的节点尽可能靠左。
In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible
# AVL树
定义:
AVL树:高度平衡的二叉搜索树(任意节点的两个子树的高度差 1)
平衡因子:某个节点的右子树高度-左子树高度
因此,AVL树又可作如下定义:任意节点平衡因子的绝对值小于等于1的二叉搜索树
当插入或删除节点导致不平衡时,AVL通过旋转节点来重新平衡节点。
# 旋转时发生了什么
如图,设根节点的左子节点高度为,右子节点高度为,下面以右旋为例
右旋以后,左子节点的高度为,右子节点高度为,观察可知
显然右旋时,需要根据来进行分类讨论。
# 插入/删除操作
同样以右旋为例
# LL型
假设插入后使得当前子树失衡,且
由于插入前是AVL树,因此
对进行一次右旋,新的树有
删除操作同理
# LR型
假设插入后使得当前子树失衡,且
如果直接右旋,会导致
因此要先对进行一次左旋,再对进行右旋
删除操作同理
# Java实现及测试代码
实现了插入、删除操作
关键字不可重复
# 优化
插入优化:
在插入单个一个新节点时,从被插入的节点到树的根节点这一路径上所有节点的(
平衡因子
)都可能会被改变,因此需要回溯更新。但如果某一节点的更新后为,则说明以为根的这一子树高度没有发生变化,因此到这些节点不需要再更新。
删除优化:
再删除一个节点以后,从被删除的节点到根节点路径上的都可能改变,因此要回溯更新。
由于删除单个节点,子树的高度最多减1;如果某一节点的从变为了,那么说明的高度没有变化,因此后续节点不需要再回溯更新。
# 参考资料
- https://en.wikipedia.org/wiki/AVL_tree
# B树(B-树)
B树是一种自平衡多叉树。
性质:对于一个阶B树:
- 所有叶子节点在同一层
- 每个节点至多有个子树
- 除根/叶子节点外,每个节点至少有个子树,所有节点的子树个数(即
出度数
)= 关键码个数 + 1 - 根节点至少有两个子树
也就是说,根节点的关键字数量满足,非根节点关键字数量满足
阶B树也被称为 (, )树。如,则称为2-3树
# 插入与删除
# 插入
插入时总是插入到叶子节点中。当节点的关键字个数超过时,需要进行调整。
下图是一个3-5
树,此时坐下的节点不符合B树性质,需要进行分裂
取中间的值(即23),将其放入父节点中,并将原节点分裂成两部分
若分裂后父节点的关键字个数大于4,则需要进一步分裂父节点。
# 删除
删除时需要考虑的情况较多,记为非根节点关键字个数下限,即
删除的是叶子节点中的关键字
删除后未破坏B树性质:无需处理
删除后破坏B树性质(关键字个数小于):
如果兄弟节点(左侧或右侧的任意一个)的关键字个数大于:
先将父节点的一个关键字移动到当前节点(前驱或后继)
然后,从兄弟节点中取一个关键字移动到父节点中,删除操作完成
如果兄弟节点的关键字个数等于:
先将父节点的一个关键字移动到当前节点
由于,因此可以将当前节点与兄弟节点合并
若此时父节点关键字个数不满足B树性质,则需要继续向上修正
需要特别说明的是:
- 如果能够从兄弟节点借,此后父节点必定满足B树性质,无需继续向上修正
- 因此,若对非叶子节点进行修正,则之前执行的必定是合并操作
删除的是非叶子结点中的关键字
此时需要将关键字替换为后继(或前驱),并在子节点中删除该关键字。这个过程有点类似
BST
的删除操作。
# 优点
在磁盘等介质上进行读写时,如果树的深度过大则会产生大量的IO操作,因此需要减少树的高度。
同时,B树能够更的利用磁盘成块(扇区)读写的性质
# Java实现
实现了插入与删除功能
默认实现2-3
树
关键字不可重复,代码内有注释以及assert
作为提示。
测试代码与AVL树的类似,略
# 参考资料
- https://my.oschina.net/u/4116286/blog/3107389 (删除部分有误)
- https://blog.csdn.net/qq_41078889/article/details/108756708
- https://github.com/phishman3579/java-algorithms-implementation
- https://en.wikipedia.org/wiki/B-tree
# B+树
B+树与B树类似,其不同点如下:
- 叶子节点保存关键字的全部信息,且叶子节点之间依据关键字大小相互链接(链表)
- 非叶子结点可以看做索引,存放的是对应子树中最小/最大关键字(一说是直接子节点的最小/最大关键字)
关于关键字个数的限制,在查阅资料后,发现有两种说法:
- 说法一:
- 内部节点的关键字个数等于子节点个数
- 说法二:
- 内部节点的关键字个数等于子节点个数-1(同B树)
另外,中文Wiki与英文Wiki中关于每个节点中关键字的最低数量有冲突,而英文wiki的参考资料又与wiki冲突,建议自行取舍。
# 优点
由于内部节点不需要保存完整信息,因此单一节点可以保存更多信息,IO次数更少。
叶节点形成链表,在范围查询时不需要进行中序遍历
查询数据必定要查找到叶子节点,性能稳定(这算优点???)
# 实现
与B树唯一的差别在于:
- 添加关键字时,若关键字个数过多需要分裂,要注意仅叶子节点需要保存值
- 删除关键字时,对于叶子节点,由于有兄弟节点的"指针",因此可以不经父节点获取到兄弟节点
- 添加关键字时要沿途更新索引,删除关键字后要向根节点方向更新索引
# 参考资料
- https://www.geeksforgeeks.org/introduction-of-b-tree/
- https://en.wikipedia.org/wiki/B%2B_tree
- https://zh.wikipedia.org/wiki/B%2B%E6%A0%91
- https://segmentfault.com/a/1190000020416577
# 红黑树
红黑树是一棵自平衡二叉树,满足以下性质
任何节点要么是红色,要么是黑色
根节点是黑色(不重要)
所有叶子节点(NIL,不含任何数据)是黑色的
如果一个节点是红色的,则其子节点是黑色的
任意一个点到其子孙叶子节点的所有路径中,黑节点个数相同
由性质4/5可以看出:从根节点到所有叶子节点的路径中,最长的长度小于等于最短的两倍
一颗含有个节点的红黑树高度至多为
# 插入操作
插入时,先按普通二叉树的方式插入,并把插入的节点染成红色,此时显然不会违背性质1/2/3/5,因此只需检查性质4
当父节点是黑色节点
满足性质4,插入完成
当父节点是红色节点
由于根节点必定是黑色节点(性质二),而父节点是红色节点,因此必定有祖父节点(黑色),进而必定有叔叔节点(内部节点或NIL节点)
叔叔节点是红色
将父节点、叔叔节点染为黑色,将祖父节点染为红色,并继续检查祖父节点是否满足性质4
叔叔节点是黑色
LR/RL型:在当前节点上进行一次旋转,使之变成LL/RR型
LL/RR型:
对父节点进行一次旋转,并把父节点染成黑色,同时为了避免性质5被破坏,将原祖父节点染为红色(由于原叔叔节点是黑色,原兄弟节点是黑色,因而不会破坏性质4)
解决问题的关键在于不断将多余的红色节点向根节点方向移动
# 删除操作(较难理解,附图)
(删除总比插入难)
二叉树删除节点时,若被删除节点有2个非空子节点,总可以通过与前驱/后继交换值的方法,转换为删除至多只有一个非空孩子的节点,因此接下来只讨论删除至多只有一个非空孩子的节点
若该节点是红色的,删除它并不会破坏红黑树的性质,因而无需额外操作
若该节点是黑色的,且它的子节点是红色的,此时只需将子节点染为黑色即可保持性质5
若该节点是黑色的,且他的子节点是黑色时的情况:
此时该节点的子节点必定都是NIL(否则,一子节点为非叶子结点,此时违反性质5)
将该节点删去(用NIL代替),为了维持性质5,我们暂且认为新的NIL等价于2个黑色节点(图中用正方形表示)
如上图所示,图中使用蓝色代表黑色节点,白色代表黑色或红色节点(不得不吐槽一句,Visio真的不好用)
下面开始分类讨论
brother
为黑色,且brother
有一个红色子节点,如下图所示如果红色节点与其父节点、祖父节点形成LR/RL型结构,则需要进行一次旋转(并重新染色)转化为LL/RR型,如上图(1)所示
对于LL/RR型,则旋转兄弟节点,原父节点与兄弟节点交换颜色,并将子节点染为黑色(如上图(2)所示),相当于从兄弟节点借了一个黑色节点
brother
为黑色,且无红色子节点如图所示,若
parent
为红色,则重新染色即可否则,将特殊节点上移并重新染色,若
parent
不为根节点,则需要继续对parent
进行修复若
brother
为红色如图所示,旋转
brother
并重新染色,此时node
的兄弟节点必定是黑色,转化为情况1/2
# Java实现
测试未通过
# 参考资料
https://en.wikipedia.org/wiki/Red%E2%80%93black_tree
https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91
https://www.cnblogs.com/skywang12345/p/3245399.html
https://www.tianxiaobo.com/2018/01/11/%E7%BA%A2%E9%BB%91%E6%A0%91%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/
https://segmentfault.com/a/1190000012115424(推荐阅读)
# 其他树(线段树/LinkCutTree/Splay等)
见 算法学习-数据结构 一栏