文章目录
- 1 什么是红黑树
- 1.1 红黑树的背景
- 1.2 红黑树的特性 ★★★
- 2 红黑树的Java实现
- 2.1 红黑树颜色枚举类Color
- 2.2 红黑树节点类Node
- 2.2.1 实现判断是否是左孩子方法isLeftChild()
- 2.2.2 实现查找叔叔节点方法uncle()
- 2.2.3 实现查找兄弟节点方法sibling()
- 2.3 红黑树类RedBlackTree
- 2.3.1 实现判断是否为红色节点方法isRed(Node node)
- 2.3.2 实现判断是否为黑色节点方法isBlack(Node node)
- 2.3.3 实现右旋方法rightRotate(Node pink)
- 2.3.4 实现左旋方法leftRotate(Node pink)
- 2.3.5 实现新增或更新方法put(int key, Object value) ★
- 2.3.6 实现删除方法remove(int key) ★
- 3 红黑树Java实现代码完整版(复制粘贴用)
- 二叉搜索树小结
🙊前言:本文章为瑞_系列专栏之《数据结构与算法》的红黑树篇。由于博主是从B站黑马程序员的《数据结构与算法》学习到的相关知识,所以本系列专栏主要针对该课程进行笔记总结和拓展,文中的部分原理及图解也是来源于黑马提供的资料。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!
1 什么是红黑树
1.1 红黑树的背景
红黑树是一种自平衡二叉查找树,最早由一位名叫Rudolf Bayer的德国计算机科学家于1972年发明。然而,最初的树形结构不是现在的红黑树,而是一种称为B树的结构,它是一种多叉树,可用于在磁盘上存储大量数据。
在1980年代早期,计算机科学家Leonard Adleman和Daniel Sleator推广了红黑树,并证明了它的自平衡性和高效性。从那时起,红黑树成为了最流行的自平衡二叉查找树之一,并被广泛应用于许多领域,如编译器、操作系统、数据库等。
红黑树的名字来源于红色节点和黑色节点的交替出现,它们的颜色是用来维护树的平衡性的关键。它们的颜色具有特殊的意义,黑色节点代表普通节点,而红色节点代表一个新添加的节点,它们必须满足一些特定的规则才能维持树的平衡性。
红黑树也是一种自平衡的二叉搜索树,较之 AVL树,插入和删除时旋转次数更少。所以红黑树和AVL的区别主要在于判断平衡的依据不同。
无论是AVL树还是红黑树,都是为了确保树的平衡性,从而提高搜索、插入和删除操作的效率
。
关于AVL树的相关知识,可以参考《瑞_数据结构与算法_AVL树》
1.2 红黑树的特性 ★★★
- 所有节点都有两种颜色:红🔴、黑⚫️
- 所有 null 视为黑色⚫️
- 红色🔴节点不能相邻
- 根节点是黑色⚫️
- 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样(黑色完美平衡)
第3条、第5条很重要,是判断平衡的主要依据。且当一个叶子节点没有兄弟的时候,就需要考虑null值(null 视为黑色)。
判断1:❌如下二叉树不是平衡的红黑树❌,违反了第3条(红色🔴节点不能相邻)
判断2:❌如下二叉树不是平衡的红黑树❌,违反了第5条,根节点6到7和9路径中的黑色节点数为3,而根节点6到1和3路径中的黑色节点数为2,所以右边重,左边轻
判断3:✅如下二叉树是一颗平衡的红黑树✅5个特性均满足
判断4:❌如下二叉树不是平衡的红黑树❌,注意区别判断3。其实当一个叶子节点没有兄弟的时候,就需要考虑null值(null 视为黑色)
上图加上null值(视为黑色)后,如下所示,根节点6到节点1的左右孩子(null)的路径中的黑色节点有3个,而根节点6到节点2的右孩子路径中的黑色节点只有2个,所以违反了特性5(从根到任意一个叶子节点,路径中的黑色⚫️节点数一样)
瑞:所以在判断是否为红黑树的时候,当一个叶子节点没有兄弟的时候,就需要考虑null值(null 视为黑色),得出以下经验
1️⃣叶子节点如果为红色🔴,是可以单独存在的(可以没有兄弟),但是如果叶子节点为黑色⚫️没有兄弟的,就是不平衡的。
: two:红色🔴节点的孩子肯定是黑色⚫️,而且如果红色节点有孩子一定是两个黑色节点,因为如果只有一个黑色节点孩子意味着违反了特性5
2 红黑树的Java实现
1️⃣内部颜色枚举类Color
中含有:
- RED
- BLACK
2️⃣➖1️⃣内部节点类Node
中含有属性:
- 索引
- 存储值
- 左孩子
- 右孩子
- 父节点
- 颜色Color枚举类
2️⃣➖2️⃣内部节点类Node
中含有内部工具方法:
- 判断是否是左孩子isLeftChild()
- 查找叔叔节点uncle()
- 查找兄弟节点sibling()
3️⃣➖1️⃣红黑树类RedBlackTree
中含有属性:
- 根节点(Node)
3️⃣➖2️⃣红黑树类RedBlackTree
中含有方法:
- 判断是否为红色节点isRed(Node node)
- 判断是否为黑色节点isBlack(Node node)
- 右旋rightRotate(Node pink)
- 左旋leftRotate(Node pink)
- 新增或更新put(int key, Object value)
- 修复新增中不平衡的情况fixRedRed(Node x)
- 删除remove(int key)
- 判断节点是否存在contains(int key)
- 查找删除节点find(int key)
- 查找剩余节点findReplaced(Node deleted)
- 处理双黑fixDoubleBlack(Node x)
2.1 红黑树颜色枚举类Color
enum Color {
RED, BLACK;
}
2.2 红黑树节点类Node
static class Node {
/**
* 索引
*/
int key;
/**
* 存储值
*/
Object value;
/**
* 左孩子
*/
Node left;
/**
* 右孩子
*/
Node right;
/**
* 父节点
*/
Node parent;
/**
* 颜色(新节点初始值默认红色)
*/
Color 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() {
// 如果父节点为null或者爷爷节点为null,是没有叔叔节点的
if (parent == null || parent.parent == null) {
return null;
}
// 如果父亲是爷爷的左孩子,叔叔则是爷爷的右孩子
if (parent.isLeftChild()) {
return parent.parent.right;
} else {
// 如果父亲是爷爷的右孩子,叔叔则是爷爷的左孩子
return parent.parent.left;
}
}
// 查找兄弟节点
Node sibling() {
// 如果父亲为null,一定没有兄弟节点
if (parent == null) {
return null;
}
// 如果当前节点是父亲的左孩子,兄弟节点是父亲的右孩子
if (this.isLeftChild()) {
return parent.right;
} else {
// 如果当前节点是父亲的右孩子,兄弟节点是父亲的左孩子
return parent.left;
}
}
}
2.2.1 实现判断是否是左孩子方法isLeftChild()
// 判断是否是左孩子
boolean isLeftChild() {
return parent != null && parent.left == this;
}
2.2.2 实现查找叔叔节点方法uncle()
叔叔节点即父节点的兄弟节点
// 查找叔叔节点(父节点的兄弟节点)
Node uncle() {
// 如果父节点为null或者爷爷节点为null,是没有叔叔节点的
if (parent == null || parent.parent == null) {
return null;
}
// 如果父亲是爷爷的左孩子,叔叔则是爷爷的右孩子
if (parent.isLeftChild()) {
return parent.parent.right;
} else {
// 如果父亲是爷爷的右孩子,叔叔则是爷爷的左孩子
return parent.parent.left;
}
}
2.2.3 实现查找兄弟节点方法sibling()
// 查找兄弟节点
Node sibling() {
// 如果父亲为null,一定没有兄弟节点
if (parent == null) {
return null;
}
// 如果当前节点是父亲的左孩子,兄弟节点是父亲的右孩子
if (this.isLeftChild()) {
return parent.right;
} else {
// 如果当前节点是父亲的右孩子,兄弟节点是父亲的左孩子
return parent.left;
}
}
2.3 红黑树类RedBlackTree
2.3.1 实现判断是否为红色节点方法isRed(Node node)
// 判断是否为红色节点
boolean isRed(Node node) {
return node != null && node.color == Color.RED;
}
2.3.2 实现判断是否为黑色节点方法isBlack(Node node)
// 判断是否为黑色节点
boolean isBlack(Node node) {
return node == null || node.color == Color.BLACK;
}
2.3.3 实现右旋方法rightRotate(Node pink)
类似AVL树的右旋方法,思想基本一致,但是要多处理父节点属性,同时意味着旋转后的新根的父子关系需要处理。关于AVL树的右旋,可以参考《瑞_数据结构与算法_AVL树》
假设一颗红黑树向右旋转前,如下图所示:
- 粉红色节点,旧根(失衡节点)
- 黄色节点,旧根的左孩子,将来作为新根,旧根是它右孩子
- 绿色节点,新根的右孩子,将来要换爹作为旧根的左孩子
右旋后,如下图所示:
实现代码如下:
// 右旋 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;
}
}
2.3.4 实现左旋方法leftRotate(Node pink)
与右旋方法思想一样,代码如下:
// 左旋
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;
}
}
2.3.5 实现新增或更新方法put(int key, Object value) ★
与AVL树的put方法的思想类似,但要多考虑红红不平衡的情况,需要进行调整(红黑树特性3 红色🔴节点不能相邻)
瑞:红黑树恢复平衡一般通过两种情况:变色、旋转。关于失衡的四种情况LL、LR、RL、RR,以及解决失衡的左旋(RR)、右旋(LL)、先左旋再右旋(LR)、先右旋再左旋(RL)可以参考《瑞_数据结构与算法_AVL树》(建议先学会AVL树再学习红黑树)
插入情况分为4种,具体如下:
- 默认插入节点均视为红色🔴
1️⃣ 插入节点为根节点,将根节点变黑⚫️
2️⃣ 插入节点的父亲若为黑色⚫️,树的红黑性质不变,无需调整
- 插入节点的父亲为红色🔴,触发红红相邻,违反特性3
3️⃣ 叔叔为红色🔴
3️⃣➖1️⃣ 父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️
3️⃣➖2️⃣祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色🔴
3️⃣➖3️⃣ 祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整
4️⃣ 叔叔为黑色⚫️
4️⃣➖1️⃣ 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡
4️⃣➖1️⃣➖1️⃣ 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
4️⃣➖1️⃣➖2️⃣ 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
4️⃣➖2️⃣ 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡
4️⃣➖2️⃣➖1️⃣ 父亲左旋,变成 LL 情况,按 1. 来后续处理
4️⃣➖3️⃣ 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡
4️⃣➖3️⃣➖1️⃣ 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
4️⃣➖3️⃣➖1️⃣ 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
4️⃣➖4️⃣ 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡
4️⃣➖4️⃣➖1️⃣ 父亲右旋,变成 RR 情况,按 3. 来后续处理
实现代码如下:
/**
* 新增或更新
* <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);
}
/**
* 修复新增中不平衡的情况
*
* @param x 新增节点
**/
void fixRedRed(Node x) {
// case 1 插入节点是根节点,变黑即可
if (x == root) {
x.color = 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 = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
fixRedRed(grandparent);
return;
}
// case 4 当红红相邻,叔叔为黑时
if (parent.isLeftChild() && x.isLeftChild()) { // LL
parent.color = Color.BLACK;
grandparent.color = Color.RED;
rightRotate(grandparent);
} else if (parent.isLeftChild()) { // LR
leftRotate(parent);
x.color = Color.BLACK;
grandparent.color = Color.RED;
rightRotate(grandparent);
} else if (!x.isLeftChild()) { // RR
parent.color = Color.BLACK;
grandparent.color = Color.RED;
leftRotate(grandparent);
} else { // RL
rightRotate(parent);
x.color = Color.BLACK;
grandparent.color = Color.RED;
leftRotate(grandparent);
}
}
2.3.6 实现删除方法remove(int key) ★
与AVL树的remove方法的思想类似,但要多考虑黑黑不平衡的情况,需要进行调整
瑞:红黑树恢复平衡一般通过两种情况:变色、旋转。关于失衡的四种情况LL、LR、RL、RR,以及解决失衡的左旋(RR)、右旋(LL)、先左旋再右旋(LR)、先右旋再左旋(RL)可以参考《瑞_数据结构与算法_AVL树》(建议先学会AVL树再学习红黑树)
删除情况(case)分为6种,具体如下:
0️⃣ 如果删除节点有两个孩子,化简成只有一个孩子或没有孩子
0️⃣➖1️⃣ 交换删除节点和后继节点的 key,value,递归删除后继节点,直到该节点没有孩子或只剩一个孩子
瑞:使用这个技巧,就可以将两个孩子的问题转换为一个孩子或没有孩子的情况
- 如果删除节点没有孩子或只剩一个孩子
1️⃣ 删的是根节点
1️⃣➖1️⃣ 删完了,直接将 root = null
1️⃣➖2️⃣ 用剩余节点替换了根节点的 key,value,根节点孩子 = null,颜色保持黑色⚫️不变
- 删黑色会失衡,删红色不会失衡,但删黑色有一种简单情况
2️⃣ 删的是黑⚫️,剩下的是红🔴,剩下这个红节点变黑⚫️
瑞:只要是删除黑色节点,都会失衡。解决方法:兄弟变为红色,父亲变为黑色。中间的红色节点的删除不用考虑失衡,因为删中间的红色节点最终都会转化为删黑色节点,因为红色节点如果有孩子,就只能有两个黑色节点孩子(一个黑孩子意味着违反特性5),就会转化为情况0️⃣,变为一个孩子或者没有孩子的情况,就会和后继进行交换,此时其实删除的就是黑色节点。
- 删除节点和剩下节点都是黑⚫️,触发双黑,双黑意思是,少了一个黑(整个路径上缺少一个黑导致失衡)
3️⃣ 被调整节点的兄弟为红🔴,此时两个侄子定为黑 ⚫️
3️⃣➖1️⃣ 删除节点是左孩子,父亲左旋
3️⃣➖2️⃣ 删除节点是右孩子,父亲右旋
3️⃣➖3️⃣ 父亲和兄弟要变色,保证旋转后颜色平衡
3️⃣➖4️⃣ 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入 case 4️⃣ 或 case 5️⃣
case3️⃣相当于是一种过渡情况,并不能通过调整自己恢复平衡,需要将case3️⃣转换为case4️⃣或case5️⃣,进一步调整
瑞:由于调整,全部黑色节点的红黑树是可能出现的情况,通过变色可以解决情况4
4️⃣ 被调整节点的兄弟为黑⚫️,两个侄子都为黑 ⚫️
4️⃣➖1️⃣ 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少 1
4️⃣➖2️⃣ 如果父亲是红🔴,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
4️⃣➖3️⃣ 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑
情况5通过变色解决不了平衡问题,还需要通过旋转才能平衡
5️⃣ 被调整节点的兄弟为黑⚫️,至少一个红🔴侄子
5️⃣➖1️⃣ 如果兄弟是左孩子,左侄子是红🔴,LL 不平衡
5️⃣➖1️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️
5️⃣➖1️⃣➖2️⃣ 原来兄弟要成为父亲,需要保留父亲颜色
5️⃣➖2️⃣ 如果兄弟是左孩子,右侄子是红🔴,LR 不平衡
5️⃣➖2️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
5️⃣➖2️⃣➖2️⃣ 右侄子会取代原来父亲,因此它保留父亲颜色
5️⃣➖2️⃣➖3️⃣ 兄弟已经是黑了⚫️,无需改变
5️⃣➖3️⃣ 如果兄弟是右孩子,右侄子是红🔴,RR 不平衡
5️⃣➖3️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️
5️⃣➖3️⃣➖1️⃣ 原来兄弟要成为父亲,需要保留父亲颜色
5️⃣➖4️⃣ 如果兄弟是右孩子,左侄子是红🔴,RL 不平衡
5️⃣➖4️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
5️⃣➖4️⃣➖2️⃣ 左侄子会取代原来父亲,因此它保留父亲颜色
5️⃣➖4️⃣➖3️⃣ 兄弟已经是黑了⚫️,无需改变
实现代码如下:
/**
* 删除
* <br>
* 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整
*
* @param key 键
*/
public void remove(int key) {
Node deleted = find(key);
if (deleted == null) {
return;
}
doRemove(deleted);
}
// 查找删除节点
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;
}
// 判断节点是否存在
public boolean contains(int key) {
return find(key) != 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 = Color.RED;
sibling.color = Color.BLACK;
fixDoubleBlack(x);
return;
}
if (sibling != null) {
// case 4 兄弟是黑色, 两个侄子也是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
sibling.color = Color.RED;
if (isRed(parent)) {
parent.color = Color.BLACK;
} else {
fixDoubleBlack(parent);
}
}
// case 5 兄弟是黑色, 侄子有红色
else {
// LL
if (sibling.isLeftChild() && isRed(sibling.left)) {
rightRotate(parent);
sibling.left.color = 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 = Color.BLACK;
sibling.color = parent.color;
}
parent.color = 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; // help gc
if (isBlack(deleted) && isBlack(replaced)) {
// 复杂处理 @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑
fixDoubleBlack(replaced);
} else {
// case 2 删除是黑,剩下是红
replaced.color = 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);
}
3 红黑树Java实现代码完整版(复制粘贴用)
/**
* <h3>红黑树</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 = 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() {
// 如果父节点为null或者爷爷节点为null,是没有叔叔节点的
if (parent == null || parent.parent == null) {
return null;
}
// 如果父亲是爷爷的左孩子,叔叔则是爷爷的右孩子
if (parent.isLeftChild()) {
return parent.parent.right;
} else {
// 如果父亲是爷爷的右孩子,叔叔则是爷爷的左孩子
return parent.parent.left;
}
}
// 查找兄弟节点
Node sibling() {
// 如果父亲为null,一定没有兄弟节点
if (parent == null) {
return null;
}
// 如果当前节点是父亲的左孩子,兄弟节点是父亲的右孩子
if (this.isLeftChild()) {
return parent.right;
} else {
// 如果当前节点是父亲的右孩子,兄弟节点是父亲的左孩子
return parent.left;
}
}
}
// 判断是否为红色节点
boolean isRed(Node node) {
return node != null && node.color == Color.RED;
}
// 判断是否为黑色节点
boolean isBlack(Node node) {
// return !isRed(node);
return node == null || node.color == 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);
}
/**
* 修复新增中不平衡的情况
*
* @param x 新增节点
**/
void fixRedRed(Node x) {
// case 1 插入节点是根节点,变黑即可
if (x == root) {
x.color = 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 = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
fixRedRed(grandparent);
return;
}
// case 4 当红红相邻,叔叔为黑时
if (parent.isLeftChild() && x.isLeftChild()) { // LL
parent.color = Color.BLACK;
grandparent.color = Color.RED;
rightRotate(grandparent);
} else if (parent.isLeftChild()) { // LR
leftRotate(parent);
x.color = Color.BLACK;
grandparent.color = Color.RED;
rightRotate(grandparent);
} else if (!x.isLeftChild()) { // RR
parent.color = Color.BLACK;
grandparent.color = Color.RED;
leftRotate(grandparent);
} else { // RL
rightRotate(parent);
x.color = Color.BLACK;
grandparent.color = Color.RED;
leftRotate(grandparent);
}
}
/**
* 删除
* <br>
* 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整
*
* @param key 键
*/
public void remove(int key) {
Node deleted = find(key);
if (deleted == null) {
return;
}
doRemove(deleted);
}
// 查找删除节点
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;
}
// 判断节点是否存在
public boolean contains(int key) {
return find(key) != 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 = Color.RED;
sibling.color = Color.BLACK;
fixDoubleBlack(x);
return;
}
if (sibling != null) {
// case 4 兄弟是黑色, 两个侄子也是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
sibling.color = Color.RED;
if (isRed(parent)) {
parent.color = Color.BLACK;
} else {
fixDoubleBlack(parent);
}
}
// case 5 兄弟是黑色, 侄子有红色
else {
// LL
if (sibling.isLeftChild() && isRed(sibling.left)) {
rightRotate(parent);
sibling.left.color = 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 = Color.BLACK;
sibling.color = parent.color;
}
parent.color = 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; // help gc
if (isBlack(deleted) && isBlack(replaced)) {
// 复杂处理 @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑
fixDoubleBlack(replaced);
} else {
// case 2 删除是黑,剩下是红
replaced.color = 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);
}
}
二叉搜索树小结
二叉搜索树可以参考《瑞_数据结构与算法_二叉搜索树》
AVL树可以参考《瑞_数据结构与算法_AVL树》
二叉搜索树对比如下:
维度 | 普通二叉搜索树 | 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的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。
红黑树是一种近似平衡的二叉搜索树,它在保持高度平衡的同时,又能够保持较高的插入删除效率。红黑树通过节点着色和旋转操作来维护平衡。红黑树在维护平衡的过程中,能够进行较少的节点旋转操作,因此插入删除效率较高,并且查询效率也较高。
综上所述,红黑树具有较高的综合性能,是一种广泛应用的数据结构。
如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~