1 B树
- B树是一种平衡的多路搜索树,多用于文件系统、数据库的实现
- 1 个节点可以存储超过 2 个元素、可以拥有超过 2 个子节点
- 拥有二叉搜索树的一些性质
- 平衡,每个节点的所有子树高度一致
- 比较矮
- m阶B树的性质(m≥2)
- 所谓的m阶,就表示节点的度最多为m
- 根节点中可以只有一个元素,且只有两个子树
- 对于非根节点,有特殊的要求,比根节点的元素和子树个数都要多,至少要有ceiling(m/2)-1个元素,ceiling(m/2)个子树
- 根据子树个数的范围,重新命名B树
- 例如4阶B树,也可以叫做(2,3)树,或2-3-4树,也就是(ceiling(m/2),m)树
- B树与二叉搜索树之间的关系
- B树和二叉搜索树,在逻辑上是等价的,B树可以通过对二叉搜索树以一定的规则进行多代合并而得到
- 多代节点合并,可以获得一个超级节点,此处注意,虽然多代节点合并可以得到B树,但对于不同情况,合并逻辑并不相同,具体怎么合并才能得到,教程中没有讲述
- 2代合并的超级节点,最多拥有 4 个子节点,4个子节点的B树,至少是 4阶B树,最多是8阶B树
- 3代合并的超级节点,最多拥有 8 个子节点(至少是 8阶B树)
- n代合并的超级节点,最多拥有 2 n 个子节点( 至少是 2 n 阶B树)
- m阶B树,最多需要 log 2 m 代合并
- 其实我理解,红黑树就是一种指定了虚拟合并规则的二叉搜索树,这样可以保证该树可以合并成B树,从而尽量保证整个数的平衡性
1.1 B树的添加
- 由二叉搜索树的特点,可以想象B树中,新添加的元素也必定是添加到叶子节点
- 当将元素插入到叶子节点中后,导致叶子节点中元素个数超过B数的限制(元素个数<=m-1),例如4B树中节点中元素个数达到4个,这种现象称为上溢
- 上溢的解决方案
- 上溢节点的元素个数必然等于 m
- 假设上溢节点最中间元素的位置为 k,将 k 位置的元素向上与父节点合并
- 将 [0, k-1] 和 [k + 1, m - 1] 位置的元素分裂成 2 个子节点
- 这 2 个子节点的元素个数,必然都不会低于最低限制(┌ m/2 ┐ − 1)
- 一次分裂完毕后,有可能导致父节点上溢,依然按照上述方法解决,最极端的情况,有可能一直分裂到根节点
1.2 B树的删除
- 删除叶子节点:直接删除
- 删除非叶子节点
- 通过观察可以知道对于B树来讲,非叶子节点,一定是有左子节点,因此它的前驱节点一定是左子节点的最右侧,因此前驱节点和后继节点,一定在叶子节点中
- 因此可以先找到前驱或后继元素,覆盖所需删除元素的值
- 再把前驱或后继元素删除
- 叶子节点被删掉一个元素后,元素个数可能会低于最低限制(元素个数≥ ┌ m/2 ┐ − 1),这种现象称为下溢
- 下溢的解决方案
- 下溢节点的元素数量必然等于 ┌ m/2 ┐ − 2
- 如果下溢节点临近的兄弟节点,有至少 ┌ m/2 ┐ 个元素,可以向其借一个元素
- 将父节点的元素 b 插入到下溢节点的 0 位置(最小位置)
- 用兄弟节点的元素 a(最大的元素)替代父节点的元素 b
- 这种操作其实就是:旋转
- 如果下溢节点临近的兄弟节点,只有 ┌ m/2 ┐ − 1 个元素,也就是说无法借元素给下溢节点
- 将父节点的元素 b 挪下来跟左右子节点进行合并
- 合并后的节点元素个数等于┌ m/2 ┐ + ┌ m/2 ┐ − 2,不超过 m − 1
- 这个操作可能会导致父节点下溢,依然按照上述方法解决,下溢现象可能会一直往上传播
2 红黑树
- 红黑树也是一种自平衡的二叉搜索树,以前也叫做平衡二叉B树(Symmetric Binary B-tree)
- 红黑树必须满足的 5 条性质
- 节点只能是红色或黑色
- 根节点必须是黑色
- 叶子节点都是黑色
- 不能有连续两个连着的红色节点
- 从任一节点到叶子节点的所有路径都包含相同数目的黑节点
- 红黑树和4阶B树(2-3-4树)具有等价性,其黑色节点和红色子节点融合,就形成了与其等价的B树的节点
- 几个英文单词
- parent:父节点
- sibling:兄弟节点(父节点相同的两个节点)
- uncle:叔父节点( parent 的兄弟节点)
- grand:祖父节点( parent 的父节点)
2.1 红黑树的添加
- 已知B树中,新元素必定是添加到叶子节点中,所以红黑树可以添加的节点位置只有如下12个位置
- 建议新添加的节点默认为 RED ,这样能够让红黑树的性质尽快满足(性质 1、2、3、5 都满足,性质 4 不一定)
- 如果添加的是根节点,染成 BLACK 即可
- 按添加节点后,是否满足红黑树的性质 4,将添加分为如下两种情况
- 满足性质4:当parent 为 BLACK,不用做任何处理
- 不满足性质4:parent为RED,其中前四种又同时属于B树节点上溢的情况
- 对8种不满足性质4的处理
- 父节点为红色,但uncle节点不是红色,这表示该节点的新增不会导致上溢,此时只需要维持红黑树颜色的相关特性即可
- LL或RR的情况:parent染成黑色、grand染成红色、grand右旋转或左旋转
- LR后RL的情况:自身染成黑色、grand染成红色、parent左旋转(右旋转)、grand再右旋转(左旋转)
- 父节点为红色,且uncle节点也是红色,这表示新增节点会导致上溢
- 将parent和uncle染成黑色,grand染成红色,当做是新添加的节点进行处理,递归传给afterAdd
2.2 红黑树的删除
- B树中,最后真正被删除的元素都在叶子节点中,因此只有如下8种情况
- 如果要删除的节点为红色,直接删除即可,是不进行恢复平衡处理的,即remove方法中不会调用afterRemove
- 如果要删除的节点为黑色
- 删除有两个红色子节点的黑色节点
- 不会出现这种情况,因为根据之前而叉搜索树中删除节点的逻辑,度为2的节点,是一定是先找到它的前驱或后继节点覆盖它,最后实际上删除的还是其前驱或后继节点
- 删除拥有一个红色子节点的黑色节点(76、46)
- 此时真正删除的正是该黑色节点,此时应该将替换这个黑节点的红色子节点,传入afterRemove中
- 此时只要判断传入的节点是红色的,那么就属于当前情况,直接将这个替代的节点染成黑色即可
- 删除黑色的叶子节点(88),也就是删除后会导致下溢的情况
- 如果其兄弟节点(sibling)为黑色,且至少有一个红色节点,说明其兄弟节点可以借节点给他
- 进行旋转操作
- 旋转之后的中心节点继承 parent 的颜色
- 旋转之后的左右节点染为 BLACK
- 如果其兄弟节点(sibling)为黑色,但没有一个RED子节点,即兄弟节点无法借节点给他,也就是B树中需要父节点下沉的情况
- 将sibling染成红色,然后将parent染成黑色
- 如果parent为黑色,那么说明该parent下沉后,parent所在节点也会下溢,此时应该再次将parent节点传给afterRemove方法
- 如果兄弟节点为红色
- 其实这也意味着,sibling一定是父节点的子节点,从二叉搜索树的角度,考虑,图中相当于46过低,需要将46向上提,也就是46是80的LL的情况,因此应该对80(parent)进行右旋转
- 这种情况其兄弟节点是一定有右侧子黑色节点的,因为如果没有,那么80-88是两个黑色,80-76才能也是两个黑色
- 此时为了保证红黑树的颜色,需要将父节点染红,兄弟节点染黑
- 此时又变成了兄弟节点是黑色的情况
2.3 红黑树代码
package com.mj.tree;
import java.util.Comparator;
public class RBTree<E> extends BBST<E> {
private static final boolean RED = false;
private static final boolean BLACK = true;
public RBTree() {
this(null);
}
public RBTree(Comparator<E> comparator) {
super(comparator);
}
@Override
protected void afterAdd(Node<E> node) {
Node<E> parent = node.parent;
if (parent == null) {
black(node);
return;
}
if (isBlack(parent)) return;
Node<E> uncle = parent.sibling();
Node<E> grand = red(parent.parent);
if (isRed(uncle)) {
black(parent);
black(uncle);
afterAdd(grand);
return;
}
if (parent.isLeftChild()) {
if (node.isLeftChild()) {
black(parent);
} else {
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else {
if (node.isLeftChild()) {
black(node);
rotateRight(parent);
} else {
black(parent);
}
rotateLeft(grand);
}
}
@Override
protected void afterRemove(Node<E> node) {
if (isRed(node)) {
black(node);
return;
}
Node<E> parent = node.parent;
if (parent == null) return;
boolean left = parent.left == null || node.isLeftChild();
Node<E> sibling = left ? parent.right : parent.left;
if (left) {
if (isRed(sibling)) {
black(sibling);
red(parent);
rotateLeft(parent);
sibling = parent.right;
}
if (isBlack(sibling.left) && isBlack(sibling.right)) {
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
afterRemove(parent);
}
} else {
if (isBlack(sibling.right)) {
rotateRight(sibling);
sibling = parent.right;
}
color(sibling, colorOf(parent));
black(sibling.right);
black(parent);
rotateLeft(parent);
}
} else {
if (isRed(sibling)) {
black(sibling);
red(parent);
rotateRight(parent);
sibling = parent.left;
}
if (isBlack(sibling.left) && isBlack(sibling.right)) {
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
afterRemove(parent);
}
} else {
if (isBlack(sibling.left)) {
rotateLeft(sibling);
sibling = parent.left;
}
color(sibling, colorOf(parent));
black(sibling.left);
black(parent);
rotateRight(parent);
}
}
}
private Node<E> color(Node<E> node, boolean color) {
if (node == null) return node;
((RBNode<E>)node).color = color;
return node;
}
private Node<E> red(Node<E> node) {
return color(node, RED);
}
private Node<E> black(Node<E> node) {
return color(node, BLACK);
}
private boolean colorOf(Node<E> node) {
return node == null ? BLACK : ((RBNode<E>)node).color;
}
private boolean isBlack(Node<E> node) {
return colorOf(node) == BLACK;
}
private boolean isRed(Node<E> node) {
return colorOf(node) == RED;
}
@Override
protected Node<E> createNode(E element, Node<E> parent) {
return new RBNode<>(element, parent);
}
private static class RBNode<E> extends Node<E> {
boolean color = RED;
public RBNode(E element, Node<E> parent) {
super(element, parent);
}
@Override
public String toString() {
String str = "";
if (color == RED) {
str = "R_";
}
return str + element.toString();
}
}
}
2.3 为何说红黑树是平衡的
- 之前的5条性质可以保证 红黑树 等价于 4阶B树
- 相比AVL树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的2倍,因为如果一条路径上的黑色节点有2个,那么最长路径上的黑色节点也只能有两个,且由于两个红色节点不能挨着,因此最多就是红色和黑色相间隔的情况,也就是其2倍
- 红黑树的最大高度是 2 ∗ log 2 (n + 1) ,依然是 O(logn) 级别
- 平均时间复杂度
- 搜索:O(logn)
- 添加:O(logn),O(1) 次的旋转操作
- 删除:O(logn),O(1) 次的旋转操作
2.4 AVL树 vs 红黑树
- AVL树
- 平衡标准比较严格:每个左右子树的高度差不超过1
- 最大高度是 1.44 ∗ log 2 (n + 2) − 1.328 (100W个节点,AVL树最大树高28)
- 搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整
- 红黑树
- 平衡标准比较宽松:没有一条路径会大于其他路径的2倍
- 最大高度是 2 ∗ log 2 (n + 1) ( 100W个节点,红黑树最大树高40)
- 搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整
- 选择
- 搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树
- 相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树
- 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树