• 数据结构与算法【红黑树】的Java实现+图解


    前言

    建议先阅读普通二叉搜索树与平衡二叉搜索树的文章。理解一些基本的二叉树知识数据结构与算法【二叉搜索树】Java实现-CSDN博客

    介绍

    红黑树也是一种自平衡的二叉搜索树,较之 AVL,插入和删除时旋转次数更少。

    首先介绍代码实现会用到的概念

    • 兄弟节点:具有同一个父结点的一对节点可以互称为兄弟节点
    • 叔叔节点:父结点的兄弟节点

    红黑树特性

    1. 所有节点都有两种颜色:红🔴、黑⚫️

    2. 所有 null 视为黑色⚫️

    3. 红色🔴节点不能相邻

    4. 根节点是黑色⚫️

    5. 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样

    根据该特性,我们可以总结出

    红色节点要么没有孩子要么有两个黑孩子

    举例

    以下情况均不属于红黑树

    红红相邻

    不满足从根节点到任意叶子节点路径中的黑色节点个数相同,到达1、3节点黑色节点个数为2,而到7、9的黑色节点个数为3。

    当叶子节点不存在兄弟节点这种情况时。需要加入null值,而null值充当黑色节点。

    因此,将该图补充完整后如下

    节点2的叶子节点与其他叶子节点路径上的黑色个数不同。因此也不能称为红黑树。

    但是下图就属于红黑树

    这种情况下,即使将null值加上,也满足红黑树

    实现

    大体框架

    1. public class RedBlackTree {
    2. //需要红黑两种颜色
    3. enum Color {
    4. RED, BLACK;
    5. }
    6. Node root;
    7. static class Node {
    8. int key;
    9. Object value;
    10. Node left;
    11. Node right;
    12. Node parent; // 父节点
    13. Color color = RED; // 颜色
    14. public Node(int key, Object value) {
    15. this.key = key;
    16. this.value = value;
    17. }
    18. public Node(int key, Color color) {
    19. this.key = key;
    20. this.color = color;
    21. }
    22. public Node(int key, Color color, Node left, Node right) {
    23. this.key = key;
    24. this.color = color;
    25. this.left = left;
    26. this.right = right;
    27. if (left != null) {
    28. left.parent = this;
    29. }
    30. if (right != null) {
    31. right.parent = this;
    32. }
    33. }
    34. // 对于父结点来说,自己是否是左孩子
    35. boolean isLeftChild() {
    36. return parent != null && parent.left == this;
    37. }
    38. // 获取叔叔节点
    39. Node uncle() {
    40. //根节点与根节点的左右孩子均不存在叔叔节点
    41. if (parent == null || parent.parent == null) {
    42. return null;
    43. }
    44. //如果父亲节点是左孩子
    45. if (parent.isLeftChild()) {
    46. //返回父亲节点的兄弟节点
    47. return parent.parent.right;
    48. } else {
    49. return parent.parent.left;
    50. }
    51. }
    52. // 获取兄弟
    53. Node sibling() {
    54. //根节点不存在兄弟节点
    55. if (parent == null) {
    56. return null;
    57. }
    58. //如果该节点是左孩子
    59. if (this.isLeftChild()) {
    60. //返回右孩子
    61. return parent.right;
    62. } else {
    63. return parent.left;
    64. }
    65. }
    66. }
    67. // 判断红
    68. boolean isRed(Node node) {
    69. return node != null && node.color == RED;
    70. }
    71. // 判断黑
    72. boolean isBlack(Node node) {
    73. return node == null || node.color == BLACK;
    74. }
    75. //与平衡二叉搜索树的旋转相比,多了一步修改各个节点的parent属性修改
    76. private void rightRotate(Node node) {
    77. //被旋转节点的父结点
    78. Node parent = node.parent;
    79. //获取新的子树父结点
    80. Node newNode = node.left;
    81. //将新的子树父结点的右孩子充当旋转节点的左孩子,腾出新父节点的右孩子位置给被旋转节点
    82. Node rightChild = newNode.right;
    83. if (rightChild != null) {
    84. //如果右孩子不是null,那么将右孩子的父结点设置为被旋转节点
    85. rightChild.parent = node;
    86. }
    87. node.left = rightChild;
    88. //将新的子树节点的右孩子设置为被旋转节点
    89. newNode.right = node;
    90. //将新父节点的parent属性设置为被旋转节点的parent
    91. newNode.parent = parent;
    92. //将被旋转节点的parent属性设置为新的父结点
    93. node.parent = newNode;
    94. //修改被旋转节点的父结点的孩子属性
    95. if (parent == null) {
    96. //说明被旋转的节点为根节点
    97. root = newNode;
    98. } else if (parent.left == node) {//如果被旋转节点是父结点的左孩子
    99. //将新的左孩子设置为新的子树父结点
    100. parent.left = newNode;
    101. } else {
    102. parent.right = newNode;
    103. }
    104. }
    105. // 左旋
    106. private void leftRotate(Node node) {
    107. Node parent = node.parent;
    108. Node newNode = node.right;
    109. Node leftChild = newNode.left;
    110. if (leftChild != null) {
    111. leftChild.parent = node;
    112. }
    113. newNode.left = node;
    114. newNode.parent = parent;
    115. node.right = leftChild;
    116. node.parent = newNode;
    117. if (parent == null) {
    118. root = newNode;
    119. } else if (parent.left == node) {
    120. parent.left = newNode;
    121. } else {
    122. parent.right = newNode;
    123. }
    124. }
    125. }

    展示一下右旋的流程

    旋转不需要进行变色,只需要修改移动节点的parent属性以及left或是right属性。

    接下来针对红黑树特性,实现插入和删除代码

    实现插入的功能

    1. /**
    2. * 新增或更新
    3. * 正常增、遇到红红不平衡进行调整
    4. *
    5. * @param key 键
    6. * @param value 值
    7. */
    8. public void put(int key, Object value) {
    9. Node p = root;
    10. Node parent = null;
    11. //找到新增位置与父结点位置
    12. while (p != null) {
    13. parent = p;
    14. if (key < p.key) {
    15. p = p.left;
    16. } else if (p.key < key) {
    17. p = p.right;
    18. } else {
    19. p.value = value; // 更新
    20. return;
    21. }
    22. }
    23. Node inserted = new Node(key, value);
    24. if (parent == null) {
    25. //说明向根节点更新数据
    26. root = inserted;
    27. } else if (key < parent.key) {
    28. parent.left = inserted;
    29. inserted.parent = parent;
    30. } else {
    31. parent.right = inserted;
    32. inserted.parent = parent;
    33. }
    34. //新增完成后,进行修正红黑树
    35. fixRedRed(inserted);
    36. }

    修正红黑树方法fixRedRed()存在四种情况:

    首先需要知道的是插入节点均视为红色🔴

    case 1:插入节点为根节点,将根节点变黑⚫️

    case 2:插入节点的父亲若为黑色⚫️,树的红黑性质不变,无需调整

    插入节点的父亲为红色🔴,触发红红相邻,红红相邻又分为case 3与case 4两种情况

    case 3:叔叔为红色🔴

    • 父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️

    • 祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色🔴

    • 祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整

    图示如下

    接下来需要插入节点 1

    触发红红相邻,且叔叔节点 4 也为红色

    此时,不满足从根节点到任意叶子节点路径上黑色节点个数相同的条件,因此,需要把祖父节点 3变成红色

    此时又触发了红红相邻,因此再次执行相同操作。

    到达根节点时,将根节点变色就是实现了红黑树调整

    case 4:叔叔为黑色⚫️

    1、父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡

    • 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
    • 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻

    2、父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡

    • 父亲左旋,变成 LL 情况,按 1. 来后续处理

    3、父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡

    • 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色

    • 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻

    4、父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡

    • 父亲右旋,变成 RR 情况,按 3. 来后续处理

    图示如下

    接下来去添加节点2

    触发红红相邻,经过调整后如下图所示

    此时不平衡,需要进行一次右旋,旋转后结果如下

    1. private void fixRedRed(Node x) {
    2. // case 1 插入节点是根节点,变黑即可
    3. if (x == root) {
    4. x.color = BLACK;
    5. return;
    6. }
    7. // case 2 插入节点父亲是黑色,无需调整
    8. if (isBlack(x.parent)) {
    9. return;
    10. }
    11. // case 3 当红红相邻,叔叔为红时
    12. // 需要将父亲、叔叔变黑、祖父变红,然后对祖父做递归处理
    13. Node parent = x.parent;
    14. Node uncle = x.uncle();
    15. Node grandparent = parent.parent;
    16. if (isRed(uncle)){
    17. parent.color = BLACK;
    18. uncle.color = BLACK;
    19. grandparent.color = RED;
    20. //如果祖父与祖父的父亲也触发了红红相邻,那么递归修改祖父,直到不再触发红红相邻
    21. fixRedRed(grandparent);
    22. return;
    23. }
    24. // case 4 当红红相邻,叔叔为黑时
    25. if (parent.isLeftChild() && x.isLeftChild()) { // LL
    26. parent.color = BLACK;
    27. grandparent.color = RED;
    28. rightRotate(grandparent);
    29. } else if (parent.isLeftChild()) { // LR
    30. leftRotate(parent);
    31. x.color = BLACK;
    32. grandparent.color = RED;
    33. rightRotate(grandparent);
    34. } else if (!x.isLeftChild()) { // RR
    35. parent.color = BLACK;
    36. grandparent.color = RED;
    37. leftRotate(grandparent);
    38. } else { // RL
    39. rightRotate(parent);
    40. x.color = BLACK;
    41. grandparent.color = RED;
    42. leftRotate(grandparent);
    43. }
    44. }

    实现删除的功能

    删之前我们需要清楚红黑树一个特性:删黑色会失衡,删红色不会失衡

    一共存在下面几种情况:

    一;如果删除的是叶子节点

    如果是红色节点,直接删除就好

    case0:如果删除节点有两个孩子

    • 交换删除节点和后继节点的 key,value,递归删除后继节点,直到该节点没有孩子或只剩一个孩子

    图示如下

    比如说要删除节点 8,那么找到 8 的后继节点 9,并交换 8 与 9 的key与value

    接下来就相当于删除叶子节点了。

    case 1:

    • 删的是根节点
      • 删完了,直接将 root = null

      • 用剩余节点替换了根节点的 key,value,根节点孩子 = null,颜色保持黑色⚫️不变

    case 2:删的是黑⚫️,剩下的是红🔴,剩下这个红节点变黑⚫️

    图示如下

    接下来去删除节点 2。

    节点 3 顶替节点 2 的位置,但此时违背从根节点到叶子节点的黑色节点个数相同。因此,需要将剩下的这个节点修改为黑色

    调整节点和剩下节点都是黑⚫️,触发双黑,双黑意思是,少了一个黑

    case 3:被调整节点的兄弟为红🔴,此时两个侄子定为黑 ⚫️

    • 删除节点是左孩子,父亲左旋

    • 删除节点是右孩子,父亲右旋

    • 父亲和兄弟要变色,保证旋转后颜色平衡

    • 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入 case 4 或 case 5

    图示如下

    接下来要删除节点 4。父亲节点 6 左旋。

    旋转过后颜色不平衡,需要修改被删除节点的原兄弟节点 8 与原父结点 6 的颜色。

    此时删除节点 4 时,仍然会触发双黑,但是此时触发双黑走的是另一个逻辑case4或case5

    case 4:被调整节点的兄弟为黑⚫️,两个侄子都为黑 ⚫️

    • 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少 1

    • 如果父亲是红🔴,则需将父亲变为黑,避免红红,此时路径黑节点数目不变

    • 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑

    当父结点为红色的情况,图示如下

    将被调整节点 4 的兄弟节点 7 变色为红色,但此时节点6,7不调整的话触发双红,因此需要将父结点修改为黑色

    修改过后,可以直接将删除节点去除。

    以上情况是父结点为红色的情况,如果父结点为黑色,那么调整父结点颜色也无法使红黑树平衡。下面是父结点为黑色的情况。

    需要删除节点 1。将兄弟节点 3 变红。但时父结点为黑色节点,那么将父结点作为调整节点再次执行双黑代码

    被调整节点 2 仍满足case4的情况,因此将兄弟节点修改为红色。但父结点 4 依然是黑色节点,那么将父结点 4 作为新的被调整节点,执行双黑代码

    此时被调整节点 4 仍满足case4的情况,因此将兄弟节点修改为红色。虽然父结点8仍然是黑色节点,但由于已经是根节点,因此结束触发双黑的代码。最后调整结果如下

    case 5:被调整节点的兄弟为黑⚫️,至少一个红🔴侄子

    • 如果兄弟是左孩子,左侄子是红🔴,LL 不平衡

      • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️

      • 原来兄弟要成为父亲,需要保留父亲颜色

    • 如果兄弟是左孩子,右侄子是红🔴,LR 不平衡

      • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️

      • 右侄子会取代原来父亲,因此它保留父亲颜色

      • 兄弟已经是黑了⚫️,无需改变

    • 如果兄弟是右孩子,右侄子是红🔴,RR 不平衡

      • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️

      • 原来兄弟要成为父亲,需要保留父亲颜色

    • 如果兄弟是右孩子,左侄子是红🔴,RL 不平衡

      • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️

      • 左侄子会取代原来父亲,因此它保留父亲颜色

      • 兄弟已经是黑了⚫️,无需改变

    接下来查看一种LL的情况,图示如下

    首先要删除的节点为 4。删除后LL不平衡,因此对节点 3 进行一次右旋。

    旋转过后,需要对节点颜色进行修改,首先是原来的兄弟节点 2 ,修改为原来的父亲节点 3 的颜色。新的两个孩子节点1 ,3修改为黑色。

    再来看一种LR的情况,图示如下

    要删除节点 4 ,需要将兄弟节点 1 进行一次左旋。

    然后再将父结点 3 进行一次右旋

    旋转过后,可以看到,原本兄弟节点 1 的右孩子 2 变成了新的父结点,因此,需要将 2 的颜色修改为原本的父结点 3 的颜色,将原本的父结点 3 的颜色修改为黑色。

    具体实现代码如下

    1. public void remove(int key) {
    2. //得到被删除节点
    3. Node deleted = find(key);
    4. if (deleted == null) {
    5. return;
    6. }
    7. doRemove(deleted);
    8. }
    9. private void doRemove(Node deleted) {
    10. //替代被删除节点的节点
    11. Node replaceNode = findReplaced(deleted);
    12. Node parent = deleted.parent;
    13. //首先进行判断,如果要删除的节点是叶子节点
    14. if (replaceNode == null) {
    15. //如果是根节点
    16. if (deleted == root) {
    17. root = null;
    18. } else {
    19. if (isBlack(deleted)) {
    20. //需要进行调整
    21. fixDoubleBlack(deleted);
    22. }
    23. //如果不是根节点,判断是父结点的左孩子还是右孩子
    24. if (deleted.isLeftChild()) {
    25. parent.left = null;
    26. } else {
    27. parent.right = null;
    28. }
    29. }
    30. return;
    31. }
    32. //如果被删除节点存在一个孩子
    33. if (deleted.left == null || deleted.right == null) {
    34. if (deleted == root) {
    35. //如果是根节点,那么让该子节点直接顶替root节点即可
    36. root.key = replaceNode.key;
    37. root.value = replaceNode.value;
    38. replaceNode.parent = root.left = root.right = null;
    39. } else {
    40. if (deleted.isLeftChild()) {
    41. //如果被删除节点是父结点的左孩子
    42. parent.left = replaceNode;
    43. } else {
    44. parent.right = replaceNode;
    45. }
    46. replaceNode.parent = parent;
    47. deleted.left = deleted.parent = deleted.right = null;
    48. //将被删除节点删除后,判断是否需要进行调整
    49. if (isBlack(deleted) && isBlack(replaceNode)) {
    50. //如果删除节点与后驱节点都为黑色,那么删除一个黑色会导致黑色节点个数不同(这种情况不存在)
    51. fixDoubleBlack(replaceNode);
    52. } else {
    53. //如果被删除节点与后驱节点是一红一黑,那么都只需要将后驱节点颜色设置为黑色即可
    54. replaceNode.color = BLACK;
    55. }
    56. }
    57. return;
    58. }
    59. //说明有两个孩子,replaceNode是该节点的后驱节点,这里我们采用值交换的方法来实现删除节点
    60. int t = deleted.key;
    61. deleted.key = replaceNode.key;
    62. replaceNode.key = t;
    63. Object value = deleted.value;
    64. deleted.value = replaceNode.value;
    65. replaceNode.value = value;
    66. //经过交换后,此时被删除节点只有一个孩子或是没有孩子。再次执行删除操作
    67. doRemove(replaceNode);
    68. }
    69. /**
    70. * 触发双黑的调整
    71. * @param node 被调整的节点
    72. */
    73. private void fixDoubleBlack(Node node) {
    74. if (node == root){
    75. return;
    76. }
    77. //被调整节点的父亲节点
    78. Node parent = node.parent;
    79. //被调整节点的兄弟节点
    80. Node sibling = node.sibling();
    81. //case 3代码,目的是调整红黑树为case4或case5的情况
    82. if (isRed(sibling)){
    83. if (node.isLeftChild()){
    84. leftRotate(parent);
    85. }else {
    86. rightRotate(parent);
    87. }
    88. parent.color = RED;
    89. sibling.color =BLACK;
    90. fixDoubleBlack(node);
    91. return;
    92. }
    93. if (sibling!=null){
    94. //case 4
    95. if (isBlack(sibling.left) && isBlack(sibling.right)){
    96. sibling.color = RED;
    97. if (isRed(parent)){
    98. parent.color = BLACK;
    99. }else {
    100. fixDoubleBlack(parent);
    101. }
    102. }else {
    103. //case 5
    104. // LL
    105. if (sibling.isLeftChild() && isRed(sibling.left)) {
    106. rightRotate(parent);
    107. sibling.left.color = BLACK;
    108. sibling.color = parent.color;
    109. }
    110. // LR
    111. else if (sibling.isLeftChild() && isRed(sibling.right)) {
    112. sibling.right.color = parent.color;
    113. leftRotate(sibling);
    114. rightRotate(parent);
    115. }
    116. // RL
    117. else if (!sibling.isLeftChild() && isRed(sibling.left)) {
    118. sibling.left.color = parent.color;
    119. rightRotate(sibling);
    120. leftRotate(parent);
    121. }
    122. // RR
    123. else {
    124. leftRotate(parent);
    125. sibling.right.color = BLACK;
    126. sibling.color = parent.color;
    127. }
    128. parent.color = BLACK;
    129. }
    130. }
    131. }
    132. private Node find(int key) {
    133. Node p = root;
    134. while (p != null) {
    135. if (p.key > key) {
    136. p = p.left;
    137. } else if (key > p.key) {
    138. p = p.right;
    139. } else {
    140. return p;
    141. }
    142. }
    143. return null;
    144. }
    145. // 查找剩余节点或是后继节点
    146. private Node findReplaced(Node deleted) {
    147. if (deleted.left == null && deleted.right == null) {
    148. return null;
    149. }
    150. if (deleted.left == null) {
    151. return deleted.right;
    152. }
    153. if (deleted.right == null) {
    154. return deleted.left;
    155. }
    156. Node s = deleted.right;
    157. while (s.left != null) {
    158. s = s.left;
    159. }
    160. return s;
    161. }

    完整代码

    1. public class RedBlackTree {
    2. enum Color {
    3. RED, BLACK;
    4. }
    5. Node root;
    6. static class Node {
    7. int key;
    8. Object value;
    9. Node left;
    10. Node right;
    11. Node parent; // 父节点
    12. Color color = RED; // 颜色
    13. public Node(int key, Object value) {
    14. this.key = key;
    15. this.value = value;
    16. }
    17. public Node(int key, Color color) {
    18. this.key = key;
    19. this.color = color;
    20. }
    21. public Node(int key, Color color, Node left, Node right) {
    22. this.key = key;
    23. this.color = color;
    24. this.left = left;
    25. this.right = right;
    26. if (left != null) {
    27. left.parent = this;
    28. }
    29. if (right != null) {
    30. right.parent = this;
    31. }
    32. }
    33. // 是否是左孩子
    34. boolean isLeftChild() {
    35. return parent != null && parent.left == this;
    36. }
    37. // 获取叔叔节点
    38. Node uncle() {
    39. //根节点与根节点的左右孩子均不存在叔叔节点
    40. if (parent == null || parent.parent == null) {
    41. return null;
    42. }
    43. //如果父亲节点是左孩子
    44. if (parent.isLeftChild()) {
    45. //返回父亲节点的兄弟节点
    46. return parent.parent.right;
    47. } else {
    48. return parent.parent.left;
    49. }
    50. }
    51. // 获取兄弟
    52. Node sibling() {
    53. //根节点不存在兄弟节点
    54. if (parent == null) {
    55. return null;
    56. }
    57. //如果该节点是左孩子
    58. if (this.isLeftChild()) {
    59. //返回右孩子
    60. return parent.right;
    61. } else {
    62. return parent.left;
    63. }
    64. }
    65. }
    66. // 判断红
    67. boolean isRed(Node node) {
    68. return node != null && node.color == RED;
    69. }
    70. // 判断黑
    71. boolean isBlack(Node node) {
    72. return node == null || node.color == BLACK;
    73. }
    74. //需要处理的是,被旋转节点的孩子节点的parent属性修改,以及被旋转节点的父结点的孩子属性修改
    75. private void rightRotate(Node node) {
    76. //被旋转节点的父结点
    77. Node parent = node.parent;
    78. //获取新的子树父结点
    79. Node newNode = node.left;
    80. //将新的子树父结点的右孩子充当旋转节点的左孩子,腾出新父节点的右孩子位置给被旋转节点
    81. Node rightChild = newNode.right;
    82. if (rightChild != null) {
    83. //如果右孩子不是null,那么将右孩子的父结点设置为被旋转节点
    84. rightChild.parent = node;
    85. }
    86. node.left = rightChild;
    87. //将新的子树节点的右孩子设置为被旋转节点
    88. newNode.right = node;
    89. //将新父节点的parent属性设置为被旋转节点的parent
    90. newNode.parent = parent;
    91. //将被旋转节点的parent属性设置为新的父结点
    92. node.parent = newNode;
    93. //修改被旋转节点的父结点的孩子属性
    94. if (parent == null) {
    95. //说明被旋转的节点为根节点
    96. root = newNode;
    97. } else if (parent.left == node) {//如果被旋转节点是父结点的左孩子
    98. //将新的左孩子设置为新的子树父结点
    99. parent.left = newNode;
    100. } else {
    101. parent.right = newNode;
    102. }
    103. }
    104. // 左旋
    105. private void leftRotate(Node node) {
    106. Node parent = node.parent;
    107. Node newNode = node.right;
    108. Node leftChild = newNode.left;
    109. if (leftChild != null) {
    110. leftChild.parent = node;
    111. }
    112. newNode.left = node;
    113. newNode.parent = parent;
    114. node.right = leftChild;
    115. node.parent = newNode;
    116. if (parent == null) {
    117. root = newNode;
    118. } else if (parent.left == node) {
    119. parent.left = newNode;
    120. } else {
    121. parent.right = newNode;
    122. }
    123. }
    124. /**
    125. * 新增或更新
    126. * 正常增、遇到红红不平衡进行调整
    127. *
    128. * @param key 键
    129. * @param value 值
    130. */
    131. public void put(int key, Object value) {
    132. Node p = root;
    133. Node parent = null;
    134. //找到新增位置与父结点位置
    135. while (p != null) {
    136. parent = p;
    137. if (key < p.key) {
    138. p = p.left;
    139. } else if (p.key < key) {
    140. p = p.right;
    141. } else {
    142. p.value = value; // 更新
    143. return;
    144. }
    145. }
    146. Node inserted = new Node(key, value);
    147. if (parent == null) {
    148. //说明向根节点更新数据
    149. root = inserted;
    150. } else if (key < parent.key) {
    151. parent.left = inserted;
    152. inserted.parent = parent;
    153. } else {
    154. parent.right = inserted;
    155. inserted.parent = parent;
    156. }
    157. //新增完成后,进行修正红黑树
    158. fixRedRed(inserted);
    159. }
    160. private void fixRedRed(Node x) {
    161. // case 1 插入节点是根节点,变黑即可
    162. if (x == root) {
    163. x.color = BLACK;
    164. return;
    165. }
    166. // case 2 插入节点父亲是黑色,无需调整
    167. if (isBlack(x.parent)) {
    168. return;
    169. }
    170. // case 3 当红红相邻,叔叔为红时
    171. // 需要将父亲、叔叔变黑、祖父变红,然后对祖父做递归处理
    172. Node parent = x.parent;
    173. Node uncle = x.uncle();
    174. Node grandparent = parent.parent;
    175. if (isRed(uncle)) {
    176. parent.color = BLACK;
    177. uncle.color = BLACK;
    178. grandparent.color = RED;
    179. //如果祖父与祖父的父亲也触发了红红相邻,那么递归修改祖父,直到不再触发红红相邻
    180. fixRedRed(grandparent);
    181. return;
    182. }
    183. // case 4 当红红相邻,叔叔为黑时
    184. if (parent.isLeftChild() && x.isLeftChild()) { // LL
    185. parent.color = BLACK;
    186. grandparent.color = RED;
    187. rightRotate(grandparent);
    188. } else if (parent.isLeftChild()) { // LR
    189. leftRotate(parent);
    190. x.color = BLACK;
    191. grandparent.color = RED;
    192. rightRotate(grandparent);
    193. } else if (!x.isLeftChild()) { // RR
    194. parent.color = BLACK;
    195. grandparent.color = RED;
    196. leftRotate(grandparent);
    197. } else { // RL
    198. rightRotate(parent);
    199. x.color = BLACK;
    200. grandparent.color = RED;
    201. leftRotate(grandparent);
    202. }
    203. }
    204. public void remove(int key) {
    205. //得到被删除节点
    206. Node deleted = find(key);
    207. if (deleted == null) {
    208. return;
    209. }
    210. doRemove(deleted);
    211. }
    212. private void doRemove(Node deleted) {
    213. //替代被删除节点的节点
    214. Node replaceNode = findReplaced(deleted);
    215. Node parent = deleted.parent;
    216. //首先进行判断,如果要删除的节点是叶子节点
    217. if (replaceNode == null) {
    218. //如果是根节点
    219. if (deleted == root) {
    220. root = null;
    221. } else {
    222. if (isBlack(deleted)) {
    223. //需要进行调整
    224. fixDoubleBlack(deleted);
    225. }
    226. //如果不是根节点,判断是父结点的左孩子还是右孩子
    227. if (deleted.isLeftChild()) {
    228. parent.left = null;
    229. } else {
    230. parent.right = null;
    231. }
    232. }
    233. return;
    234. }
    235. //如果被删除节点存在一个孩子
    236. if (deleted.left == null || deleted.right == null) {
    237. if (deleted == root) {
    238. //如果是根节点,那么让该子节点直接顶替root节点即可
    239. root.key = replaceNode.key;
    240. root.value = replaceNode.value;
    241. replaceNode.parent = root.left = root.right = null;
    242. } else {
    243. if (deleted.isLeftChild()) {
    244. //如果被删除节点是父结点的左孩子
    245. parent.left = replaceNode;
    246. } else {
    247. parent.right = replaceNode;
    248. }
    249. replaceNode.parent = parent;
    250. deleted.left = deleted.parent = deleted.right = null;
    251. //将被删除节点删除后,判断是否需要进行调整
    252. if (isBlack(deleted) && isBlack(replaceNode)) {
    253. //如果删除节点与后驱节点都为黑色,那么删除一个黑色会导致黑色节点个数不同(这种情况不存在)
    254. fixDoubleBlack(replaceNode);
    255. } else {
    256. //如果被删除节点与后驱节点是一红一黑,那么都只需要将后驱节点颜色设置为黑色即可
    257. replaceNode.color = BLACK;
    258. }
    259. }
    260. return;
    261. }
    262. //说明有两个孩子,replaceNode是该节点的后驱节点,这里我们采用值交换的方法来实现删除节点
    263. int t = deleted.key;
    264. deleted.key = replaceNode.key;
    265. replaceNode.key = t;
    266. Object value = deleted.value;
    267. deleted.value = replaceNode.value;
    268. replaceNode.value = value;
    269. //经过交换后,此时被删除节点只有一个孩子或是没有孩子。再次执行删除操作
    270. doRemove(replaceNode);
    271. }
    272. /**
    273. * 触发双黑的调整
    274. * @param node 被调整的节点
    275. */
    276. private void fixDoubleBlack(Node node) {
    277. if (node == root){
    278. return;
    279. }
    280. //被调整节点的父亲节点
    281. Node parent = node.parent;
    282. //被调整节点的兄弟节点
    283. Node sibling = node.sibling();
    284. //case 3代码,目的是调整红黑树为case4或case5的情况
    285. if (isRed(sibling)){
    286. if (node.isLeftChild()){
    287. leftRotate(parent);
    288. }else {
    289. rightRotate(parent);
    290. }
    291. parent.color = RED;
    292. sibling.color =BLACK;
    293. fixDoubleBlack(node);
    294. return;
    295. }
    296. if (sibling!=null){
    297. //case 4
    298. if (isBlack(sibling.left) && isBlack(sibling.right)){
    299. sibling.color = RED;
    300. if (isRed(parent)){
    301. parent.color = BLACK;
    302. }else {
    303. fixDoubleBlack(parent);
    304. }
    305. }else {
    306. //case 5
    307. // LL
    308. if (sibling.isLeftChild() && isRed(sibling.left)) {
    309. rightRotate(parent);
    310. sibling.left.color = BLACK;
    311. sibling.color = parent.color;
    312. }
    313. // LR
    314. else if (sibling.isLeftChild() && isRed(sibling.right)) {
    315. sibling.right.color = parent.color;
    316. leftRotate(sibling);
    317. rightRotate(parent);
    318. }
    319. // RL
    320. else if (!sibling.isLeftChild() && isRed(sibling.left)) {
    321. sibling.left.color = parent.color;
    322. rightRotate(sibling);
    323. leftRotate(parent);
    324. }
    325. // RR
    326. else {
    327. leftRotate(parent);
    328. sibling.right.color = BLACK;
    329. sibling.color = parent.color;
    330. }
    331. parent.color = BLACK;
    332. }
    333. }
    334. }
    335. private Node find(int key) {
    336. Node p = root;
    337. while (p != null) {
    338. if (p.key > key) {
    339. p = p.left;
    340. } else if (key > p.key) {
    341. p = p.right;
    342. } else {
    343. return p;
    344. }
    345. }
    346. return null;
    347. }
    348. // 查找剩余节点或是后继节点
    349. private Node findReplaced(Node deleted) {
    350. if (deleted.left == null && deleted.right == null) {
    351. return null;
    352. }
    353. if (deleted.left == null) {
    354. return deleted.right;
    355. }
    356. if (deleted.right == null) {
    357. return deleted.left;
    358. }
    359. Node s = deleted.right;
    360. while (s.left != null) {
    361. s = s.left;
    362. }
    363. return s;
    364. }
    365. }
  • 相关阅读:
    第二章 进程与线程 十二、进程同步与进程互斥
    Java之spring新手教程(包教包会)
    AWK用法全解与sed去掉sql最后一个字段哪一行的逗号
    VsCode集成Python开发环境
    代码随想录训练营Day 32|Python|Leetcode|● 738.单调递增的数字
    Java8新特性必知必会
    基于Python的信用评分卡模型建立和分析,万字详述,关注收藏
    【爬虫】get 和 post 的区别
    【linux应用开发】
    大厂敲门砖!原阿里P8架构师整理12万字Java面试技巧心得
  • 原文地址:https://blog.csdn.net/zmbwcx/article/details/134555933