# 红黑树 - 实现
# 概述
# 历史
红黑树是一种自平衡二叉查找树,最早由一位名叫 Rudolf Bayer 的德国计算机科学家于 1972 年发明。然而,最初的树形结构不是现在的红黑树,而是一种称为 B 树的结构,它是一种多叉树,可用于在磁盘上存储大量数据。
在 1980 年代早期,计算机科学家 Leonard Adleman 和 Daniel Sleator 推广了红黑树,并证明了它的自平衡性和高效性。从那时起,红黑树成为了最流行的自平衡二叉查找树之一,并被广泛应用于许多领域,如编译器、操作系统、数据库等。
红黑树的名字来源于红色节点和黑色节点的交替出现,它们的颜色是用来维护树的平衡性的关键。它们的颜色具有特殊的意义,黑色节点代表普通节点,而红色节点代表一个新添加的节点,它们必须满足一些特定的规则才能维持树的平衡性。
红黑树也是一种自平衡的二叉搜索树,较之 AVL,插入和删除时旋转次数更少
# 红黑树特性
- 所有节点都有两种颜色:红🔴、黑⚫️
- 所有 null 视为黑色⚫️
- 红色🔴节点不能相邻
- 根节点是黑色⚫️
- 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样
# 实现
# 插入情况
插入节点均视为红色🔴
case 1:插入节点为根节点,将根节点变黑⚫️
case 2:插入节点的父亲若为黑色⚫️,树的红黑性质不变,无需调整
插入节点的父亲为红色🔴,触发红红相邻
case 3:叔叔为红色🔴
父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️
祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色🔴
祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整
case 4:叔叔为黑色⚫️
- 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡
- 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
- 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
- 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡
- 父亲左旋,变成 LL 情况,按 1. 来后续处理
- 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡
- 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
- 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
- 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡
- 父亲右旋,变成 RR 情况,按 3. 来后续处理
# 删除情况
case0:如果删除节点有两个孩子
- 交换删除节点和后继节点的 key,value,递归删除后继节点,直到该节点没有孩子或只剩一个孩子
如果删除节点没有孩子或只剩一个孩子
case 1:删的是根节点
- 删完了,直接将 root = null
- 用剩余节点替换了根节点的 key,value,根节点孩子 = null,颜色保持黑色⚫️不变
删黑色会失衡,删红色不会失衡,但删黑色有一种简单情况
case 2:删的是黑⚫️,剩下的是红🔴,剩下这个红节点变黑⚫️
删除节点和剩下节点都是黑⚫️,触发双黑,双黑意思是,少了一个黑
case 3:被调整节点的兄弟为红🔴,此时两个侄子定为黑 ⚫️
- 删除节点是左孩子,父亲左旋
- 删除节点是右孩子,父亲右旋
- 父亲和兄弟要变色,保证旋转后颜色平衡
- 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入 case 4 或 case 5
case 4:被调整节点的兄弟为黑⚫️,两个侄子都为黑 ⚫️
- 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少 1
- 如果父亲是红🔴,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
- 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑
case 5:被调整节点的兄弟为黑⚫️,至少一个红🔴侄子
- 如果兄弟是左孩子,左侄子是红🔴,LL 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️
- 原来兄弟要成为父亲,需要保留父亲颜色
- 如果兄弟是左孩子,右侄子是红🔴,LR 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
- 右侄子会取代原来父亲,因此它保留父亲颜色
- 兄弟已经是黑了⚫️,无需改变
- 如果兄弟是右孩子,右侄子是红🔴,RR 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️
- 原来兄弟要成为父亲,需要保留父亲颜色
- 如果兄弟是右孩子,左侄子是红🔴,RL 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
- 左侄子会取代原来父亲,因此它保留父亲颜色
- 兄弟已经是黑了⚫️,无需改变
# 完整代码
package com.itheima.datastructure.redblacktree; | |
import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.BLACK; | |
import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.RED; | |
/** | |
* <h3 > 红黑树 & lt;/h3> | |
*/ | |
public class RedBlackTree { | |
enum Color { | |
RED, BLACK; | |
} | |
Node root; | |
static class Node { | |
int key; | |
Object value; | |
Node left; | |
Node right; | |
Node parent; // 父节点 | |
Color color = RED; // 颜色 | |
public Node(int key, Object value) { | |
this.key = key; | |
this.value = value; | |
} | |
public Node(int key) { | |
this.key = key; | |
} | |
public Node(int key, Color color) { | |
this.key = key; | |
this.color = color; | |
} | |
public Node(int key, Color color, Node left, Node right) { | |
this.key = key; | |
this.color = color; | |
this.left = left; | |
this.right = right; | |
if (left != null) { | |
left.parent = this; | |
} | |
if (right != null) { | |
right.parent = this; | |
} | |
} | |
// 是否是左孩子 | |
boolean isLeftChild() { | |
return parent != null && parent.left == this; | |
} | |
// 叔叔 | |
Node uncle() { | |
if (parent == null || parent.parent == null) { | |
return null; | |
} | |
if (parent.isLeftChild()) { | |
return parent.parent.right; | |
} else { | |
return parent.parent.left; | |
} | |
} | |
// 兄弟 | |
Node sibling() { | |
if (parent == null) { | |
return null; | |
} | |
if (this.isLeftChild()) { | |
return parent.right; | |
} else { | |
return parent.left; | |
} | |
} | |
} | |
// 判断红 | |
boolean isRed(Node node) { | |
return node != null && node.color == RED; | |
} | |
// 判断黑 | |
boolean isBlack(Node node) { | |
// return !isRed(node); | |
return node == null || node.color == BLACK; | |
} | |
// 右旋 1. parent 的处理 2. 旋转后新根的父子关系 | |
private void rightRotate(Node pink) { | |
Node parent = pink.parent; | |
Node yellow = pink.left; | |
Node green = yellow.right; | |
if (green != null) { | |
green.parent = pink; | |
} | |
yellow.right = pink; | |
yellow.parent = parent; | |
pink.left = green; | |
pink.parent = yellow; | |
if (parent == null) { | |
root = yellow; | |
} else if (parent.left == pink) { | |
parent.left = yellow; | |
} else { | |
parent.right = yellow; | |
} | |
} | |
// 左旋 | |
private void leftRotate(Node pink) { | |
Node parent = pink.parent; | |
Node yellow = pink.right; | |
Node green = yellow.left; | |
if (green != null) { | |
green.parent = pink; | |
} | |
yellow.left = pink; | |
yellow.parent = parent; | |
pink.right = green; | |
pink.parent = yellow; | |
if (parent == null) { | |
root = yellow; | |
} else if (parent.left == pink) { | |
parent.left = yellow; | |
} else { | |
parent.right = yellow; | |
} | |
} | |
/** | |
* 新增或更新 | |
* <br> | |
* 正常增、遇到红红不平衡进行调整 | |
* | |
* @param key 键 | |
* @param value 值 | |
*/ | |
public void put(int key, Object value) { | |
Node p = root; | |
Node parent = null; | |
while (p != null) { | |
parent = p; | |
if (key < p.key) { | |
p = p.left; | |
} else if (p.key < key) { | |
p = p.right; | |
} else { | |
p.value = value; // 更新 | |
return; | |
} | |
} | |
Node inserted = new Node(key, value); | |
if (parent == null) { | |
root = inserted; | |
} else if (key < parent.key) { | |
parent.left = inserted; | |
inserted.parent = parent; | |
} else { | |
parent.right = inserted; | |
inserted.parent = parent; | |
} | |
fixRedRed(inserted); | |
} | |
void fixRedRed(Node x) { | |
//case 1 插入节点是根节点,变黑即可 | |
if (x == root) { | |
x.color = BLACK; | |
return; | |
} | |
//case 2 插入节点父亲是黑色,无需调整 | |
if (isBlack(x.parent)) { | |
return; | |
} | |
/* case 3 当红红相邻,叔叔为红时 | |
需要将父亲、叔叔变黑、祖父变红,然后对祖父做递归处理 | |
*/ | |
Node parent = x.parent; | |
Node uncle = x.uncle(); | |
Node grandparent = parent.parent; | |
if (isRed(uncle)) { | |
parent.color = BLACK; | |
uncle.color = BLACK; | |
grandparent.color = RED; | |
fixRedRed(grandparent); | |
return; | |
} | |
//case 4 当红红相邻,叔叔为黑时 | |
if (parent.isLeftChild() && x.isLeftChild()) { // LL | |
parent.color = BLACK; | |
grandparent.color = RED; | |
rightRotate(grandparent); | |
} else if (parent.isLeftChild()) { // LR | |
leftRotate(parent); | |
x.color = BLACK; | |
grandparent.color = RED; | |
rightRotate(grandparent); | |
} else if (!x.isLeftChild()) { // RR | |
parent.color = BLACK; | |
grandparent.color = RED; | |
leftRotate(grandparent); | |
} else { // RL | |
rightRotate(parent); | |
x.color = BLACK; | |
grandparent.color = RED; | |
leftRotate(grandparent); | |
} | |
} | |
/** | |
* 删除 | |
* <br> | |
* 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整 | |
* | |
* @param key 键 | |
*/ | |
public void remove(int key) { | |
Node deleted = find(key); | |
if (deleted == null) { | |
return; | |
} | |
doRemove(deleted); | |
} | |
public boolean contains(int key) { | |
return find(key) != null; | |
} | |
// 查找删除节点 | |
private Node find(int key) { | |
Node p = root; | |
while (p != null) { | |
if (key < p.key) { | |
p = p.left; | |
} else if (p.key < key) { | |
p = p.right; | |
} else { | |
return p; | |
} | |
} | |
return null; | |
} | |
// 查找剩余节点 | |
private Node findReplaced(Node deleted) { | |
if (deleted.left == null && deleted.right == null) { | |
return null; | |
} | |
if (deleted.left == null) { | |
return deleted.right; | |
} | |
if (deleted.right == null) { | |
return deleted.left; | |
} | |
Node s = deleted.right; | |
while (s.left != null) { | |
s = s.left; | |
} | |
return s; | |
} | |
// 处理双黑 (case3、case4、case5) | |
private void fixDoubleBlack(Node x) { | |
if (x == root) { | |
return; | |
} | |
Node parent = x.parent; | |
Node sibling = x.sibling(); | |
//case 3 兄弟节点是红色 | |
if (isRed(sibling)) { | |
if (x.isLeftChild()) { | |
leftRotate(parent); | |
} else { | |
rightRotate(parent); | |
} | |
parent.color = RED; | |
sibling.color = BLACK; | |
fixDoubleBlack(x); | |
return; | |
} | |
if (sibling != null) { | |
//case 4 兄弟是黑色,两个侄子也是黑色 | |
if (isBlack(sibling.left) && isBlack(sibling.right)) { | |
sibling.color = RED; | |
if (isRed(parent)) { | |
parent.color = BLACK; | |
} else { | |
fixDoubleBlack(parent); | |
} | |
} | |
//case 5 兄弟是黑色,侄子有红色 | |
else { | |
// LL | |
if (sibling.isLeftChild() && isRed(sibling.left)) { | |
rightRotate(parent); | |
sibling.left.color = BLACK; | |
sibling.color = parent.color; | |
} | |
// LR | |
else if (sibling.isLeftChild() && isRed(sibling.right)) { | |
sibling.right.color = parent.color; | |
leftRotate(sibling); | |
rightRotate(parent); | |
} | |
// RL | |
else if (!sibling.isLeftChild() && isRed(sibling.left)) { | |
sibling.left.color = parent.color; | |
rightRotate(sibling); | |
leftRotate(parent); | |
} | |
// RR | |
else { | |
leftRotate(parent); | |
sibling.right.color = BLACK; | |
sibling.color = parent.color; | |
} | |
parent.color = BLACK; | |
} | |
} else { | |
// @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null | |
fixDoubleBlack(parent); | |
} | |
} | |
private void doRemove(Node deleted) { | |
Node replaced = findReplaced(deleted); | |
Node parent = deleted.parent; | |
// 没有孩子 | |
if (replaced == null) { | |
//case 1 删除的是根节点 | |
if (deleted == root) { | |
root = null; | |
} else { | |
if (isBlack(deleted)) { | |
// 双黑调整 | |
fixDoubleBlack(deleted); | |
} else { | |
// 红色叶子,无需任何处理 | |
} | |
if (deleted.isLeftChild()) { | |
parent.left = null; | |
} else { | |
parent.right = null; | |
} | |
deleted.parent = null; | |
} | |
return; | |
} | |
// 有一个孩子 | |
if (deleted.left == null || deleted.right == null) { | |
//case 1 删除的是根节点 | |
if (deleted == root) { | |
root.key = replaced.key; | |
root.value = replaced.value; | |
root.left = root.right = null; | |
} else { | |
if (deleted.isLeftChild()) { | |
parent.left = replaced; | |
} else { | |
parent.right = replaced; | |
} | |
replaced.parent = parent; | |
deleted.left = deleted.right = deleted.parent = null; | |
if (isBlack(deleted) && isBlack(replaced)) { | |
// @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑 | |
fixDoubleBlack(replaced); | |
} else { | |
//case 2 删除是黑,剩下是红 | |
replaced.color = BLACK; | |
} | |
} | |
return; | |
} | |
//case 0 有两个孩子 => 有一个孩子 或 没有孩子 | |
int t = deleted.key; | |
deleted.key = replaced.key; | |
replaced.key = t; | |
Object v = deleted.value; | |
deleted.value = replaced.value; | |
replaced.value = v; | |
doRemove(replaced); | |
} | |
} |
- 以上代码中的 TODO 未作改正
# 小结
维度 | 普通二叉搜索树 | AVL 树 | 红黑树 |
---|---|---|---|
查询 | 平均 O (logn),最坏 O (n) | O(logn) | O(logn) |
插入 | 平均 O (logn),最坏 O (n) | O(logn) | O(logn) |
删除 | 平均 O (logn),最坏 O (n) | O(logn) | O(logn) |
平衡性 | 不平衡 | 严格平衡 | 近似平衡 |
结构 | 二叉树 | 自平衡的二叉树 | 具有红黑性质的自平衡二叉树 |
查找效率 | 低 | 高 | 高 |
插入删除效率 | 低 | 中等 | 高 |
普通二叉搜索树插入、删除、查询的时间复杂度与树的高度相关,因此在最坏情况下,时间复杂度为 O (n),而且容易退化成链表,查找效率低。
AVL 树是一种高度平衡的二叉搜索树,其左右子树的高度差不超过 1。因此,它能够在 logn 的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。
红黑树是一种近似平衡的二叉搜索树,它在保持高度平衡的同时,又能够保持较高的插入删除效率。红黑树通过节点着色和旋转操作来维护平衡。红黑树在维护平衡的过程中,能够进行较少的节点旋转操作,因此插入删除效率较高,并且查询效率也较高。
综上所述,红黑树具有较高的综合性能,是一种广泛应用的数据结构。