6. 红黑树

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值