二叉搜索树(BST)
二叉搜索树具有如下特点:
- 若它的左子树不空,则左子树上所有节点的值均小于等于它的根节点的值;
- 若她的右子树不空,则右子树上所有节点的值均大于等于它的根节点的值;
- 具有递归性,排序二叉树的左子树、右子树也是排序二叉树。
下图是二叉搜索树的一个示例:
平衡二叉树(AVL)
当一组元素正好是有序的时候,排序二叉树就变成了普通的链表结构,查找效率会因此而极大降低。因此发明了平衡二叉树。
平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树的高度之差不超过1。它可以是空树。
下图是一棵平衡二叉树的示例:
B-树(Balance-tree)
B-树定义
B树是一种自平衡的多叉查找树。 B树中所有结点中孩子结点个数的最大值成为B-树的阶,通常用m表示。
作用:当数据量巨大,只能存在磁盘时,使用B树的目的就是为了减少磁盘的I/O操作。
一颗m阶的B树特点如下:
- 每个结点最多有m-1个关键字,且最多有m个分支。根节点至少有1个关键字,至少有2个分支。非根节点至少有 ceil(m/2)-1个关键字,ceil(m/2)个分支。(ceil代表向上取整)
- 如果一个结点有n-1个关键字,那么该结点有n个分支。这n-1个关键字按照递增顺序排列。
- 每个结点内各关键字互不相等且按从小到大排列。
- 叶子结点处于同一层,可以用空指针表示。
下图是一个5阶的B树:
所有非叶子节点的结构如下:
假如B树包含n个关键字,高度为h,阶数为m,则B树的高度为:
B树的高度也就是磁盘I/O的次数。
B树的非叶子节点既包含索引,又包含数据。在对B树进行查找时,找到了节点就相当于找到了数据,因此B树适用于文件存储。
B树搜索
B树的搜索和二叉搜索树类似,从根节点开始,从上往下递归的遍历树。在每一层节点上,使用二分查找法匹配目标键,或者通过键的范围来确定子树。
如图:寻找值为52的节点,从根节点开始,未命中,去右子节点中寻找,又未命中,去符合范围的最左侧子节点中找,命中就退出。
B树的插入
B-树结点的插入总是落在最后一层结点上。所有的插入操作都是从根节点开始,搜索这棵树,并找到该元素应该被插入的节点。将新元素插入到该节点需要如下步骤:
- 如果该节点上的元素数未满,则将新元素插入到该节点,并保持节点中元素的顺序。
- 如果该节点上的元素已满,则需要将该节点平均地分裂成两个节点:
- 从该节点中的元素和新元素先出一个中位数
- 小于中位数的元素放到左边节点,大于中位数的元素放到右边节点,中位数做为分隔值。
- 将分隔值插入到父节点中,这可能会导致父节点的分裂,如果分裂一直上升到根节点,那么就创建一个新的根节点,它有一个分隔值和两个子节点。
下图是一个5阶B树,我们通过顺序插入1到17,来观察节点的分裂过程:
B树的删除
B树的删除可分为下面几种情况:
- 删除叶子节点中的元素:
- 搜索要删除的元素,如果它在叶子节点上,直接将其删除
- 如果删除后产生了下溢出(键数小于最小值),则向其兄弟节点借元素。即将其父节点元素下移至当前节点,将兄弟节点中元素上移至父节点(若是左节点,上移最大元素;若是右节点,上移最小元素)
- 若兄弟节点也达到下限,则合并兄弟节点与分割键。
- 删除内部节点中的元素
- 内部节点中元素为其左右子节点的分割值,需要从左子节点最大元素或右子节点最小元素中选出一个新的分割符。被选中的分割符从原子节点中移除,作为新的分隔值替换掉被删除的元素。
- 上一步中,若左右子节点元素数均达到下限,则合并左右子节点。
- 若删除元素后,其中节点元素数小于下限,则继续合并。
下图是一个5阶B树,我们通过删除15、14、17、5四个键,来观察删除过程(基本涵盖所有情况):
B+树
当存储数据量非常大时,为了降低磁盘的I/O次数,对B树进行了优化,产生了B+树。
B+树对阶的定义跟B树一样,一棵m阶的B+树具有如下特点:
- 每个节点最少有ceil(m/2)棵子树、最多有m棵子树,节点的子树个数跟节点的关键字个数相同。根节点至少有2棵子树。
- 所有叶节点包含全部关键字,叶节点中将关键字按照顺序排列,且相邻叶节点通过指针链接。
- 非叶节点只包含它的各个子树中最大的关键字及指向子树的指针。
- 有2个重要的指针,一个指向根节点,一个指向树的最左边的叶子节点。
下图是一颗4阶B+树:
B+树的非叶子节点只存储索引,数据都存储在叶子节点上。
B+树和B-树的区别
- 关键字的数量不同:B+树中有m个关键字的节点会有m个子树,其关键字只是起到了一个索引的作用;B树中有m个关键字的节点会有m+1个子树,。
- 存储的位置不同:B+树中的数据都存储在叶子结点上,非叶子节点存储索引;B树的数据存储在每一个结点中。B树适用于文件存储,B+树适合于索引存储,查找数据时不需要遍历整个树,只是需要在对应的区间内查找。
- 查询不同:B树在找到具体的数值以后,则结束;而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。
红黑树(rbtree)
红黑树的定义
首先给出红黑树的定义:红黑树是一个自平衡的二叉查找树(BST),具有以下特点:
- 树的根始终是黑色的 (黑土地孕育黑树根)
- 每个节点只能是红色或黑色
- 没有两个连续的红色节点(红色节点不能有红色父节点或红色子节点,并没有说不能出现连续的黑色节点)
- 从节点(包括根)到其任何叶子节点(叶子结点下方挂的两个空节点,并且认为他们是黑色的)的路径都具有相同数量的黑色节点(即:黑色完美平衡)。
下图是一个红黑树的示例:
需要注意的是,红黑树并不是一个绝对平衡的二叉搜索树,而是近似平衡。红黑树从根节点到叶子节点的最长路径长度最多是最短路径的两倍。如下图所示的一棵红黑树,满足上述4条性质,但并不平衡:
2-3-4树到红黑树的转化
红黑树的本质是对2-3-4树的实现。2-3-4树是阶数为4的B树,所以2-3-4树中每个节点的关键字个数1<=n<=3,也就是说会每个节点具有的子树的个数为2<=childNum<=4。因此,我们称具有2个子树的节点为2节点;具有3个子树的节点为3节点;具有4个子树的节点为4节点,如下图所示:
我们以二叉树为基础,在二叉树中加入红色或黑色的属性来描述2-3-4树到红黑树的转化。
2-3-4树中转换成红黑树具有下图所示的规则:
为了研究方便,后面我们讨论2-3树到红黑树的转换。
为了方便比较,下图是红黑树到2-3树的转化过程图:
旋转介绍
旋转分为左旋和右旋,左旋是将某个节点旋转为其右孩子的左孩子,而右旋是将某个节点旋转为其左孩子的右孩子。
红黑树的插入
为了保持完美黑色平衡,会令待插入的节点为红色,如果出现2个连续的红色节点,可以通过变色和旋转来调平。
实际上,旋转才是红黑树调平的精髓。插入情况可以左倾红黑树的插入和右倾红黑树的插入,左倾和右倾红黑树的插入都分别有3种情况。下图是左倾红黑树插入的3种情况:
下图是右倾红黑树插入的3种情况:
由于左倾和右倾插入的调整逻辑是对称的,故只讲左倾红黑树的3种插入情况:
注意:在左倾红黑树插入时,如果待插入节点跟其父节点构成右倾红黑树,且父节点是红的,那么该节点一定要调成左倾的。
同样的道理,在右倾红黑树插入时,如果出现父节点是红的,且该节点也是红的,那么该节点一定要调成右倾的。
情况1:插入18
情况2:插入10
情况3:插入13
红黑树的插入
假设我们插入的新节点为 X,X 的父节点是 P,叔叔节点是 U,祖父节点是G:
- 将新插入的节点 X 标记为红色
- 如果 X 是根结点(root),则标记为黑色
- 如果 X 不是 root ,同时X 的 parent 是红色:
- 3.1 如果 X 的 uncle (叔叔) 是红色
- 3.1.1 将 parent 和 uncle 标记为黑色
- 3.1.2 将 grand parent (祖父) 标记为红色
- 3.1.3 让 X 节点的颜色与 X 祖父的颜色相同,然后重复步骤 2、3
- 3.2 如果 X 的 uncle (叔叔) 是黑色,我们要分四种情况处理
- 3.2.1 左左 (P 是 G 的左孩子,并且 X 是 P 的左孩子)
- 3.2.2 左右 (P 是 G 的左孩子,并且 X 是 P 的右孩子)
- 3.2.3 右右 (和 3.2.1 镜像过来,恰好相反)
- 3.2.4 右左 (和 3.2.2 镜像过来,恰好相反)
当出现 uncle 是黑色的时候我们第一步要考虑的是 旋转 。
左左
这种情况很简单,想象这是一根绳子,手提起 P 节点,然后变色即可
左右
左旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的左孩子,然后再应用 左左情况
右右
与左左情况一样,想象成一根绳子
右左
右旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的右孩子,然后再应用 右右情况
对红黑树的疑问
问题:
1、为什么红黑树不会出现连续的红色节点?
答:2-3树中本来就规定没有4节点,转换后自然不会有连续的红色节点。虽然2-3-4树中有4节点,但是要求在红黑树中体现为一黑色节点带两红色儿子,分布左右,所以也不会有连续红节点。
2、红黑树可以都是黑色节点吗?
答:可以。
3、红黑树待插入的节点为什么是红色节点?
答:为了保证黑色完美平衡。
4、红黑树为什么要区分红色和黑色?
答:为了通过旋转和变色达到平衡。
数据结构可视化网站:
参考文献: