目录
引理证明:一颗有n个内部结点的红黑树的高度至多为2lg(n+1)
情况二:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点
情况三:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点
情况二:如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是节点 a 的右子节点 c
情况三:如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的后继节点不是右子节点
情况二:如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c 的左右子节点 d、e 都是黑色的
情况三:如果关注节点是 a,它的兄弟节点 c 是黑色,c 的左子节点 d 是红色,c 的右子节点 e 是黑色
情况四:如果关注节点 a 的兄弟节点 c 是黑色的,并且 c 的右子节点是红色的
一、对红黑树的理解
(一)基本理解
红黑树是一种自平衡的二叉搜索树,它在插入和删除操作时会通过一系列的旋转和颜色调整来保持树的平衡,从而保证了在最坏情况下的查找、插入和删除操作的时间复杂度都是 O(log n),其中 n 是树中节点的数量。
红黑树具有以下特性:
- 节点颜色: 每个节点要么是红色,要么是黑色。
- 根节点: 根节点是黑色的。
- 叶子节点(NIL节点): 叶子节点是黑色的,也被称为NIL节点。它们不存储任何数据,只是作为树结构的辅助。所有指向NIL节点的指针都被认为是黑色的。
- 颜色限制: 不能有两个相连的红色节点,即红色节点不能相邻,每个红色节点的子节点都是黑色的。
- 黑高度: 从任意节点出发,到达其每个叶子节点的路径上的黑色节点数量必须相同,这被称为黑高度。这个性质保证了树的平衡。
红黑树通过旋转操作来维护平衡,主要有左旋和右旋两种操作,它们通过重新排列节点来保持或恢复树的性质。插入和删除操作可能会导致颜色违规或者黑高度违规,因此在执行这些操作后,红黑树需要通过颜色调整和旋转来恢复平衡。
总之,红黑树的平衡性质和性能保证使它在需要频繁进行插入、删除和查找操作的数据结构中表现出色,比如在各种编程语言的标准库中广泛使用,例如C++的std::map和std::set,以及Java的TreeMap和TreeSet。
(二)红黑树与AVL树的比较
红黑树(Red-Black Tree)和AVL树(Adelson-Velsky and Landis Tree)都是自平衡二叉搜索树,用于在动态数据集上进行高效的插入、删除和搜索操作。它们之间有一些相似之处,但也存在一些关键的区别。下面是红黑树和AVL树的比较:
平衡性:
- 红黑树:红黑树保证了一种弱平衡,即树的高度最多是2倍的对数级别。这使得红黑树在插入和删除操作时具有更高的灵活性,但可能导致一些操作稍微不如AVL树高效。
- AVL树:AVL树是一种严格的平衡树,保证任何节点的左子树和右子树的高度差(平衡因子)不超过1。这确保了AVL树在平衡方面表现更好,但在插入和删除操作时可能需要更多的旋转来维持平衡。
插入和删除操作的性能:
- 红黑树:由于红黑树的平衡性要求相对较弱,插入和删除操作通常需要更少的旋转操作,因此在这些操作上性能可能比AVL树更好。
- AVL树:AVL树的严格平衡性可能导致插入和删除操作需要更频繁的旋转操作,因此在这些操作上可能比红黑树略逊一筹。
搜索性能:
- 两者在搜索操作上都表现良好,因为它们都是二叉搜索树,保持了有序性。
存储空间:
- 红黑树:红黑树在维护平衡的同时,需要存储额外的颜色信息,因此通常比AVL树占用更少的存储空间。
- AVL树:AVL树由于需要维护严格的平衡,可能需要更多的额外指针和信息,因此通常比红黑树占用更多的存储空间。
适用场景:
- 红黑树:适用于在插入和删除操作较频繁、搜索操作相对较少的场景,例如在数据库索引中。
- AVL树:适用于搜索操作频繁、插入和删除操作相对较少的场景,以及对于对树的平衡性要求较高的场景。
总的来说,红黑树和AVL树都有各自的优势和适用场景。选择哪种树结构取决于应用的需求和对性能的具体要求。在实际使用中,还要考虑到具体的操作频率、数据量以及对内存占用的限制。
二、在实际框架中的应用分析
红黑树在实际的计算机科学和软件工程中有广泛的应用,特别是在需要高效地处理插入、删除和查找操作的情况下。以下是一些实际框架和应用中红黑树的应用分析:
-
C++标准库中的std::map和std::set: C++的STL(标准模板库)中提供了
std::map
和std::set
这两个容器,它们分别基于红黑树实现。std::map
是一个关联容器,它提供了键-值对的存储和查找功能,而std::set
是一个集合容器,用于存储唯一的值。红黑树在这两个容器中保证了高效的插入、删除和查找操作。 -
Java标准库中的TreeMap和TreeSet: Java的标准库中也提供了
TreeMap
和TreeSet
,它们与C++中的std::map
和std::set
类似,同样基于红黑树实现。这些容器在Java编程中广泛用于需要有序数据存储和检索的场景。 -
Linux内核中的进程调度: Linux内核中的进程调度器(例如CFS,Completely Fair Scheduler)使用了红黑树来维护进程的运行队列。红黑树的平衡性能保证了高效的进程切换和调度。
-
数据库索引: 数据库系统中的索引结构通常需要高效地支持范围查询、插入和删除操作。红黑树被广泛应用于数据库的索引结构,如B+树的实现中。
-
文件系统: 一些文件系统,特别是日志结构文件系统(Log-Structured File Systems,如LFS),可以使用红黑树来维护文件的元数据信息,如文件名和目录结构。
-
编译器优化: 在编译器优化中,使用红黑树来维护数据流分析的信息,如变量的定值分析和活跃变量分析。
-
网络路由表: 在网络路由器和交换机中,红黑树可以用于高效地管理路由表,以支持快速的路由查找和更新。
-
资源管理: 操作系统和服务器软件可能会使用红黑树来管理资源,如内存分配、文件描述符等。
总的来说,红黑树在各种领域中都有广泛的应用,主要是因为它能够在保持树的平衡的同时,提供高效的插入、删除和查找操作,从而满足了许多实际应用中对于数据结构的性能要求。
三、开始深入红黑树
(一)红黑树的基本概念和性质
当谈到红黑树的基本概念和性质时,我们通常会涉及它的定义以及保持平衡的关键性质。下面是红黑树的基本概念和性质的详细解释:
1、红黑树的基本定义
红黑树是一种自平衡的二叉搜索树,它具有以下特性:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色的。
- 每个叶子节点(NIL节点)是黑色的,这些叶子节点不存储任何数据,只是作为树结构的辅助。
- 不能有两个相连的红色节点。这意味着红色节点不能相邻,每个红色节点的两个子节点都是黑色的。
- 从任意节点出发,到达其每个叶子节点的路径上的黑色节点数量必须相同。这个性质保证了树的平衡,确保了最长路径不会超过最短路径的两倍。
2、红黑性质的五个要点
-
根节点是黑色的: 第一个性质要求根节点始终是黑色的,这确保了树的整体结构保持平衡。
-
红色子节点性质: 不能有两个相连的红色节点。这意味着从任意节点到其子节点的路径上不能出现连续的红色节点,以避免出现不平衡情况。
-
黑高度平衡性质: 从任意节点出发,到达其每个叶子节点的路径上的黑色节点数量必须相同。这确保了树的高度始终保持在一个合理的范围内,从而保证了高效的查找操作。
-
红黑性质的维护: 在执行插入和删除操作后,红黑树需要通过旋转和颜色调整来保持这些性质,从而恢复平衡。这些操作保证了在更新操作之后,树仍然是一颗满足红黑性质的树。
-
空节点的处理: 空节点(NIL节点)被认为是黑色的。这样可以确保每个路径上的黑色节点数量相等,即使是经过了空节点的路径。
这五个性质共同保证了红黑树的平衡性和高效性能。红黑树在插入、删除和查找等操作时能够保持相对较低的时间复杂度,使其在很多应用中都得到广泛应用,如数据库索引、编程语言的标准库等。
引理证明:一颗有n个内部结点的红黑树的高度至多为2lg(n+1)
这个引理可以通过归纳法来证明。我们可以利用红黑树的性质,特别是黑高度平衡性质,来得出结论。下面是证明的大致步骤:
基础情况: 当红黑树只有一个内部结点(根节点)时,树的高度为1,而2lg(1+1) = 2,因此基础情况成立。
归纳假设: 假设对于任意的有k个内部结点的红黑树,其高度至多为2lg(k+1)。
归纳步骤: 现在我们考虑有k+1个内部结点的红黑树。假设其中的一棵子树有i个内部结点,而另一棵子树有k+1-i个内部结点(这里0 <= i <= k+1)。
根据归纳假设,i个结点的子树的高度至多为2lg(i+1),而k+1-i个结点的子树的高度至多为2lg((k+1-i)+1)。
因此,整棵红黑树的高度不会超过这两个子树高度的较大值。即整棵树的高度至多为 max(2lg(i+1), 2lg((k+1-i)+1))。
我们要找到使得这个值最大的i,这个值在i=k/2时达到最大。所以,整棵红黑树的高度至多为 max(2lg(k/2+1), 2lg((k/2+1)+1))。
进一步简化这个表达式,我们可以得到整棵红黑树的高度至多为 2 + max(2lg(k/2+1), 2lg((k/2+1)+1))。
通过化简可以得到 2 + 2lg(k+1),这就证明了有k+1个内部结点的红黑树的高度至多为2lg(k+2)。
由归纳法的假设和步骤,我们可以得出结论,对于任意有n个内部结点的红黑树,其高度至多为2lg(n+1)。
(二)对旋转的基本理解
在数据结构中,旋转是一种常见的操作,用于调整树或其他数据结构的结构以保持平衡或满足某些性质。在红黑树、AVL树、二叉搜索树等数据结构中,旋转操作通常用于平衡树的结构,以确保高效的插入、删除和查找操作。旋转操作有两种基本类型:左旋和右旋。下面我将解释这两种旋转的基本理解:
1、左旋(Left Rotation)
左旋是一种将某个节点的右子节点旋转上来的操作。它会将当前节点下移,并且将其右子节点提升为新的父节点。这可以用于解决树向右倾斜的情况,以保持树的平衡。左旋的基本步骤:
- 将当前节点的右子节点设为新的父节点。
- 将新的父节点的左子节点设为当前节点的右子节点。
- 如果当前节点有父节点,将新的父节点替代当前节点的位置。
- 将当前节点设为新的父节点的左子节点。
2、右旋(Right Rotation)
右旋是一种将某个节点的左子节点旋转上来的操作。它会将当前节点下移,并且将其左子节点提升为新的父节点。这可以用于解决树向左倾斜的情况,以保持树的平衡。右旋的基本步骤:
- 将当前节点的左子节点设为新的父节点。
- 将新的父节点的右子节点设为当前节点的左子节点。
- 如果当前节点有父节点,将新的父节点替代当前节点的位置。
- 将当前节点设为新的父节点的右子节点。
旋转操作可以使树保持平衡,确保最长路径和最短路径之间的差距不会过大,从而保证了树在查找、插入和删除等操作时的高效性能。在红黑树、AVL树等自平衡树结构中,旋转是实现平衡的关键步骤之一。
3、代码展示
package org.zyf.javabasic.rbtest;
/**
* @program: zyfboot-javabasic
* @description:
* @author: zhangyanfeng
* @create: 2023-08-19 12:38
**/
public class RedBlackTree {
private TreeNode root;
public RedBlackTree() {
root = null;
}
// 左旋操作
private void leftRotate(TreeNode x) {
TreeNode y = x.right; // y 为 x 的右子节点
x.right = y.left; // 将 y 的左子节点设置为 x 的右子节点
if (y.left != null) {
y.left.parent = x;
}
y.parent = x.parent; // 更新 y 的父节点
if (x.parent == null) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x; // 将 x 设为 y 的左子节点
x.parent = y;
}
// 右旋操作
private void rightRotate(TreeNode y) {
TreeNode x = y.left; // x 为 y 的左子节点
y.left = x.right; // 将 x 的右子节点设置为 y 的左子节点
if (x.right != null) {
x.right.parent = y;
}
x.parent = y.parent; // 更新 x 的父节点
if (y.parent == null) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y; // 将 y 设为 x 的右子节点
y.parent = x;
}
}
(三)插入操作基本理解
1、以图形方式进行初步理解
红黑树规定,插入的节点必须是红色的。而且,二叉查找树中新插入的节点都是放在叶子节点上。
关于插入操作的平衡调整,有这样两种特殊情况:
- 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。
- 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。
除此之外,其他情况都会违背红黑树的定义,需要进行调整,调整的过程包含两种基础的操作:左右旋转和改变颜色。
红黑树的平衡调整过程是一个迭代的过程。把正在处理的节点叫作关注节点。关注节点会随着不停地迭代处理,而不断发生变化。最开始的关注节点就是新插入的节点。新节点插入之后,如果红黑树的平衡被打破,那一般会有下面三种情况:
备注:我们只需要根据每种情况的特点,不停地调整,就可以让红黑树继续符合定义,也就是继续保持平衡。为了简化描述,把父节点的兄弟节点叫作叔叔节点,父节点的父节点叫作祖父节点。
情况一:如果关注节点是 a,它的叔叔节点 d 是红色
具体操作为:将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色;将关注节点 a 的祖父节点 c 的颜色设置成红色;关注节点变成 a 的祖父节点 c;跳到情况二或者情况三。
情况二:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点
具体操作为:关注节点变成节点 a 的父节点 b;围绕新的关注节点b 左旋;跳到情况三。
情况三:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点
具体操作为:围绕关注节点 a 的祖父节点 c 右旋;将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换,调整结束。
2、详细步骤分析总结
当在红黑树中执行插入操作时,需要考虑两个主要方面:保持二叉搜索树性质和保持红黑性质。以下是插入操作的详细步骤,包括可能的旋转操作和颜色调整。
插入操作的基本步骤:
-
将新节点插入到BST中: 首先,将新节点插入到红黑树中,就像在普通的二叉搜索树中一样。新节点会被标记为红色,因为它可能会破坏红黑性质的第一个性质(根节点必须是黑色)。
-
检查红黑性质: 插入新节点后,可能会破坏红黑性质。需要通过一系列的操作来调整以确保所有的红黑性质得到满足。
颜色调整: 在进行旋转操作之前,需要进行颜色调整以满足红黑性质。以下是颜色调整的可能情况:
-
父节点为黑色: 如果插入的节点的父节点是黑色的,那么树的结构没有破坏,不需要进一步的调整。
-
父节点为红色: 如果插入的节点的父节点是红色的,那么可能会破坏红黑性质的第二个性质(不能有两个相连的红色节点)。在这种情况下,需要考虑插入节点的叔叔节点(父节点的兄弟节点)的颜色。
a. 叔叔节点是红色: 如果叔叔节点是红色的,可以通过改变父节点和叔叔节点的颜色,然后将问题向上移动到父节点。这样可以保持黑高度平衡性质。
b. 叔叔节点是黑色或缺失: 如果叔叔节点是黑色的(包括叔叔节点为NIL节点),需要通过旋转来修复这种情况。
旋转操作: 旋转操作用于调整树的结构,以保持红黑性质。以下是可能的旋转情况:
- 左旋和右旋: 左旋和右旋操作已在之前的回答中进行了解释。当插入节点的父节点是红色,而叔叔节点是黑色或缺失时,需要进行旋转操作来保持红黑性质。
总结: 插入操作的关键是保持红黑性质。颜色调整和旋转操作可以确保插入操作后的红黑树依然满足所有性质。需要注意的是,具体的情况会因树的结构而异,因此在编写插入操作的代码时,需要仔细考虑各种可能的情况,并根据实际情况进行相应的调整和处理。
3、代码展示说明
package org.zyf.javabasic.rbtest;
/**
* @program: zyfboot-javabasic
* @description:
* @author: zhangyanfeng
* @create: 2023-08-19 12:58
**/
public class RedBlackTreeInsert {
private TreeNode root;
enum Color {
RED, BLACK
}
class TreeNode {
int key;
Color color;
TreeNode left;
TreeNode right;
TreeNode parent;
public TreeNode(int key, Color color) {
this.key = key;
this.color = color;
this.left = null;
this.right = null;
this.parent = null;
}
}
public void insert(int key) {
TreeNode newNode = new TreeNode(key, Color.RED);
if (root == null) {
root = newNode;
} else {
TreeNode parent = findParentForInsert(root, key);
if (key < parent.key) {
parent.left = newNode;
} else {
parent.right = newNode;
}
newNode.parent = parent;
insertFixup(newNode);
}
}
private TreeNode findParentForInsert(TreeNode node, int key) {
TreeNode parent = null;
while (node != null) {
parent = node;
if (key < node.key) {
node = node.left;
} else {
node = node.right;
}
}
return parent;
}
private void insertFixup(TreeNode node) {
while (node.parent != null && node.parent.color == Color.RED) {
if (node.parent == node.parent.parent.left) {
TreeNode uncle = node.parent.parent.right;
if (uncle != null && uncle.color == Color.RED) {
node.parent.color = Color.BLACK;
uncle.color = Color.BLACK;
node.parent.parent.color = Color.RED;
node = node.parent.parent;
} else {
if (node == node.parent.right) {
node = node.parent;
leftRotate(node);
}
node.parent.color = Color.BLACK;
node.parent.parent.color = Color.RED;
rightRotate(node.parent.parent);
}
} else {
// 同上,但是左右对调
}
}
root.color = Color.BLACK;
}
private void leftRotate(TreeNode x) {
// 左旋操作的代码
}
private void rightRotate(TreeNode y) {
// 右旋操作的代码
}
}
(四)删除操作基本理解
1、以图形方式进行初步理解
删除操作的平衡调整分为两步:
- 第一步是针对删除节点初步调整。初步调整只是保证整棵红黑树在一个节点删除之后,仍然满足最后一条定义的要求,也就是说,每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;
- 第二步是针对关注节点进行二次调整,让它满足红黑树的第三条定义,即不存在相邻的两个红色节点。
第一步:针对删除节点初步调整
红黑树的定义中“只包含红色节点和黑色节点”,经过初步调整之后,为了保证满足红黑树定义的最后一条要求,有些节点会被标记成两种颜色,“红 – 黑”或者“黑 – 黑”。如果一个节点被标记为了“黑 – 黑”,那在计算黑色节点个数的时候,要算成两个黑色节点。
备注:如果一个节点既可以是红色,也可以是黑色,图中用一半红色一半黑色来表示。如果一个节点是“红 – 黑”或者“黑 – 黑”,图中用左上角的一个小黑点来表示额外的黑色。
情况一:如果要删除的节点是 a,它只有一个子节点 b
具体操作为:删除节点 a,并且把节点 b 替换到节点 a 的位置,这一部分操作跟普通的二叉查找树的删除操作一样;节点 a 只能是黑色,节点 b 也只能是红色,其他情况均不符合红黑树的定义。这种情况下,我们把节点 b 改为黑色;调整结束,不需要进行二次调整。
情况二:如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是节点 a 的右子节点 c
具体操作为:如果节点 a 的后继节点就是右子节点 c,那右子节点 c 肯定没有左子树。我们把节点 a 删除,并且将节点 c 替换到节点 a 的位置。这一部分操作跟普通的二叉查找树的删除操作无异;然后把节点 c 的颜色设置为跟节点 a 相同的颜色;如果节点 c 是黑色,为了不违反红黑树的最后一条定义,我们给节点 c 的右子节点 d 多加一个黑色,这个时候节点 d 就成了“红 – 黑”或者“黑 – 黑”;这个时候,关注节点变成了节点 d,第二步的调整操作就会针对关注节点来做。
情况三:如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的后继节点不是右子节点
具体操作为:找到后继节点 d,并将它删除,删除后继节点 d 的过程参照 CASE 1;将节点 a 替换成后继节点 d;把节点 d 的颜色设置为跟节点 a 相同的颜色;如果节点 d 是黑色,为了不违反红黑树的最后一条定义,我们给节点 d 的右子节点 c 多加一个黑色,这个时候节点 c 就成了“红 – 黑”或者“黑 – 黑”;这个时候,关注节点变成了节点 c,第二步的调整操作就会针对关注节点来做。
第二步:针对关注节点进行二次调整
初步调整之后,关注节点变成了“红 – 黑”或者“黑 – 黑”节点。针对这个关注节点,再分四种情况来进行二次调整。
备注:二次调整是为了让红黑树中不存在相邻的红色节点。
情况一:如果关注节点是 a,它的兄弟节点 c 是红色的
具体操作:围绕关注节点 a 的父节点 b 左旋;关注节点 a 的父节点 b 和祖父节点 c 交换颜色;关注节点不变;继续从四种情况中选择适合的规则来调整。
情况二:如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c 的左右子节点 d、e 都是黑色的
具体操作:将关注节点 a 的兄弟节点 c 的颜色变成红色;从关注节点 a 中去掉一个黑色,这个时候节点 a 就是单纯的红色或者黑色;给关注节点 a 的父节点 b 添加一个黑色,这个时候节点 b 就变成了“红 – 黑”或者“黑 – 黑”;关注节点从 a 变成其父节点 b;继续从四种情况中选择符合的规则来调整。
情况三:如果关注节点是 a,它的兄弟节点 c 是黑色,c 的左子节点 d 是红色,c 的右子节点 e 是黑色
具体操作:围绕关注节点 a 的兄弟节点 c 右旋;节点 c 和节点 d 交换颜色;关注节点不变;跳转到 CASE 4,继续调整。
情况四:如果关注节点 a 的兄弟节点 c 是黑色的,并且 c 的右子节点是红色的
具体操作:围绕关注节点 a 的父节点 b 左旋;将关注节点 a 的兄弟节点 c 的颜色,跟关注节点 a 的父节点 b 设置成相同的颜色;将关注节点 a 的父节点 b 的颜色设置为黑色;从关注节点 a 中去掉一个黑色,节点 a 就变成了单纯的红色或者黑色;将关注节点 a 的叔叔节点 e 设置为黑色;调整结束。
2、详细步骤分析总结
红黑树的删除操作相对比较复杂,因为删除一个节点可能会引发多种情况,需要仔细考虑旋转和颜色调整来维持红黑性质。以下是红黑树中执行删除操作的详细步骤,包括可能涉及的旋转和颜色调整。
删除操作的基本步骤:
-
找到要删除的节点: 首先,找到需要删除的目标节点。如果目标节点有两个子节点,可以选择它的后继节点(即右子树中最小的节点)来替代删除。
-
删除节点并用子节点替代: 删除目标节点,并用它的子节点(或者后继节点)来替代它的位置。
颜色调整和旋转: 在删除操作中,可能会导致红黑性质被破坏。以下是删除操作中可能需要考虑的情况:
-
删除节点为红色节点: 如果删除的节点是红色的,它不会破坏红黑性质。只需将它从树中移除即可。
-
删除节点为黑色节点: 删除的黑色节点可能会引发性质的破坏,需要进行颜色调整和旋转操作。
a. 删除节点有一个红色子节点: 如果删除的节点有一个红色子节点,用红色子节点替代删除节点,然后将子节点染成黑色,以保持黑高度性质。
b. 删除节点没有红色子节点: 如果删除的节点没有红色子节点,那么需要通过颜色调整和旋转来修复。
i. 删除节点是根节点:如果删除的节点是根节点,直接删除并返回。
ii.删除节点的兄弟节点是红色:如果删除节点的兄弟节点是红色的,可以通过旋转和颜色调整将情况转化为其他情况处理。
iii. 删除节点的兄弟节点是黑色:如果删除节点的兄弟节点是黑色的,可能需要进行旋转和颜色调整来恢复平衡。
- 双重黑色节点向兄弟节点的借:如果删除的节点是黑色的,兄弟节点是黑色的,而兄弟节点有至少一个红色子节点,可以通过旋转和颜色调整来消除双重黑色。
- 双重黑色节点没有向兄弟节点的借:如果删除的节点是黑色的,兄弟节点是黑色的,而兄弟节点没有红色子节点,需要将双重黑色节点向上传递。
总结: 删除操作在红黑树中是相对复杂的,因为涉及到多种情况的处理。颜色调整和旋转操作的目标是保持红黑性质,并确保树的结构平衡。确保你的删除操作代码能够正确处理所有可能的情况,以保证红黑树的正确性。在实际开发中,逐步编写代码并进行测试是验证删除操作的有效方法。
3、具体代码展示
package org.zyf.javabasic.rbtest;
/**
* @program: zyfboot-javabasic
* @description:
* @author: zhangyanfeng
* @create: 2023-08-19 13:10
**/
public class RedBlackTreeDelete {
private TreeNode root;
enum Color {
RED, BLACK
}
class TreeNode {
int key;
Color color;
TreeNode left;
TreeNode right;
TreeNode parent;
public TreeNode(int key, Color color) {
this.key = key;
this.color = color;
this.left = null;
this.right = null;
this.parent = null;
}
}
// 省略其他方法...
public void delete(int key) {
TreeNode nodeToDelete = findNodeToDelete(root, key);
if (nodeToDelete != null) {
deleteNode(nodeToDelete);
}
}
private void deleteNode(TreeNode nodeToDelete) {
TreeNode replacementNode = (nodeToDelete.left != null && nodeToDelete.right != null)
? findSuccessor(nodeToDelete.right)
: nodeToDelete;
TreeNode childNode = (replacementNode.left != null) ? replacementNode.left : replacementNode.right;
if (childNode != null) {
childNode.parent = replacementNode.parent;
}
if (replacementNode.parent == null) {
root = childNode;
} else if (replacementNode == replacementNode.parent.left) {
replacementNode.parent.left = childNode;
} else {
replacementNode.parent.right = childNode;
}
if (replacementNode != nodeToDelete) {
nodeToDelete.key = replacementNode.key; // 将替代节点的值赋给被删除节点
}
if (replacementNode.color == Color.BLACK) {
deleteFixup(childNode, replacementNode.parent); // 进行颜色调整和可能的旋转操作
}
}
// 寻找要删除的节点
private TreeNode findNodeToDelete(TreeNode current, int key) {
if (current == null || current.key == key) {
return current;
}
if (key < current.key) {
return findNodeToDelete(current.left, key);
}
return findNodeToDelete(current.right, key);
}
// 寻找后继节点
private TreeNode findSuccessor(TreeNode node) {
TreeNode current = node;
while (current.left != null) {
current = current.left;
}
return current;
}
private void deleteFixup(TreeNode x, TreeNode xParent) {
while (x != root && (x == null || x.color == Color.BLACK)) {
if (x == xParent.left) {
TreeNode sibling = xParent.right;
if (sibling.color == Color.RED) {
// 情况 1:兄弟节点为红色
sibling.color = Color.BLACK;
xParent.color = Color.RED;
leftRotate(xParent);
sibling = xParent.right;
}
if ((sibling.left == null || sibling.left.color == Color.BLACK) &&
(sibling.right == null || sibling.right.color == Color.BLACK)) {
// 情况 2:兄弟节点和其子节点都为黑色
sibling.color = Color.RED;
x = xParent;
xParent = xParent.parent;
} else {
if (sibling.right == null || sibling.right.color == Color.BLACK) {
// 情况 3:兄弟节点是黑色,且兄弟节点的左子节点是红色
if (sibling.left != null) {
sibling.left.color = Color.BLACK;
}
sibling.color = Color.RED;
rightRotate(sibling);
sibling = xParent.right;
}
// 情况 4:兄弟节点是黑色,且兄弟节点的右子节点是红色
sibling.color = xParent.color;
xParent.color = Color.BLACK;
if (sibling.right != null) {
sibling.right.color = Color.BLACK;
}
leftRotate(xParent);
x = root; // 完成调整,退出循环
}
} else {
// 同上,但是左右对调
}
}
if (x != null) {
x.color = Color.BLACK; // 将 x 染黑
}
}
private void leftRotate(TreeNode x) {
TreeNode y = x.right;
x.right = y.left;
if (y.left != null) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == null) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
private void rightRotate(TreeNode y) {
TreeNode x = y.left;
y.left = x.right;
if (x.right != null) {
x.right.parent = y;
}
x.parent = y.parent;
if (y.parent == null) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
}
四、源码上的分析
红黑树在很多编程语言和算法库中都有相应的实现。以下是一些常见的源码和资源:
-
C++ STL 源码分析: C++ 的标准模板库(STL)中包含了红黑树的实现,你可以阅读 STL 的源码,特别是
std::map
和std::set
的实现。 -
Java
TreeMap
和TreeSet
源码分析: Java 中的TreeMap
和TreeSet
类是基于红黑树实现的。 -
Linux 内核红黑树: Linux 内核中有红黑树的实现,用于高效地管理各种数据结构。
(一)TreeMap源码简单总结
当分析 TreeMap
的源码时,可以总结红黑树操作的主要源码部分和需要注意的事项:
红黑树操作的主要源码部分
-
插入操作 (
put
方法): 在插入新的键值对时,TreeMap
会调用put
方法来处理插入操作。在插入的过程中,会调用红黑树的旋转和颜色调整操作来保持红黑树的性质。 -
删除操作 (
remove
方法): 删除操作也会涉及到红黑树的旋转和颜色调整。当删除一个节点后,TreeMap
会通过调用红黑树的fixAfterDeletion
方法来修复性质。 -
旋转操作 (
rotateLeft
和rotateRight
方法): 旋转操作用于调整红黑树的平衡,包括左旋和右旋。在TreeMap
中,rotateLeft
方法和rotateRight
方法用于进行旋转操作。 -
颜色调整操作: 在插入和删除操作中,通过改变节点的颜色来调整红黑树的性质。
TreeMap
会根据情况调用红黑树节点的fixAfterInsertion
和fixAfterDeletion
方法来进行颜色调整。
注意事项
-
性质维护: 红黑树是一种自平衡二叉搜索树,需要保持五个红黑性质。在所有操作(插入、删除等)后,必须确保红黑性质得到满足。
-
平衡操作: 旋转操作是红黑树的关键部分,用于保持树的平衡性。
TreeMap
的rotateLeft
和rotateRight
方法用于执行左旋和右旋。 -
颜色调整: 插入和删除操作可能会导致性质被破坏,通过改变节点的颜色来恢复性质。注意在进行颜色调整时,要确保叶子节点(即 null 节点)的颜色为黑色。
-
迭代器和遍历:
TreeMap
提供了迭代器用于遍历键值对。在遍历过程中,应该按照中序遍历来访问节点,以保持有序性。 -
自定义比较器:
TreeMap
可以使用默认的键比较器(如果键类型实现了Comparable
接口),或者使用自定义的比较器。在插入和删除时,要根据比较结果来确定节点的位置。 -
性能: 尽管红黑树保持了一定的平衡性,但在极端情况下,可能会导致树高较高,影响性能。在某些情况下,考虑使用其他数据结构(如哈希表)来获得更好的性能。
(二)
Linux 内核红黑树
Linux 内核中的红黑树实现位于头文件 include/linux/rbtree.h
中。这个头文件定义了红黑树的数据结构和相关的操作函数。以下是一个简化的示例,展示了 Linux 内核中红黑树的一些关键部分:
// 内核红黑树节点结构
struct rb_node {
unsigned long __rb_parent_color;
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
// 红黑树根节点
struct rb_root {
struct rb_node *rb_node;
};
// 初始化红黑树根节点
static inline void rb_init_node(struct rb_node *rb)
{
rb->__rb_parent_color = 0;
rb->rb_right = NULL;
rb->rb_left = NULL;
RB_CLEAR_NODE(rb);
}
// 插入节点到红黑树
void rb_insert_color(struct rb_node *, struct rb_root *);
// 从红黑树中删除节点
void rb_erase(struct rb_node *, struct rb_root *);
// 红黑树的旋转操作
static inline void rb_rotate_left(struct rb_node *node, struct rb_root *root)
{
// 左旋操作实现
}
static inline void rb_rotate_right(struct rb_node *node, struct rb_root *root)
{
// 右旋操作实现
}
上面的示例是非常简化的,实际内核中的红黑树实现更加复杂和全面。在 Linux 内核中,红黑树被广泛用于管理各种数据结构,如文件系统中的目录项、网络协议栈中的路由表等。可以通过查看实际的内核源码文件来了解更多细节。
参考文献与链接:
1.红黑树深入剖析及Java实现 – 美团技术团队
2.《数据结构与算法之美》,王争(前Google工程师),极客时间,2019
3.浅析红黑树(RBTree)原理及实现_芮萌萌的博客-CSDN博客_红黑树
4.快速理解红黑树原理_jjc120074203的博客-CSDN博客_红黑树的原理
5.红黑树详解-java实现_Felix_阳的博客-CSDN博客
今天的文章对红黑树的认识总结怎么写_红黑树五大特性分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/71428.html