5.从任意一个节点到其所有后代叶子节点的简单路径上,包含相同数量的黑色节点。
红黑树(red-black tree):一棵满足红黑性质的二叉查找树。
性质3:对于一棵有 n 个内部节点的红黑树,其黑高 bh 满足不等式 bh >= log(n+1)/2。证明:由性质1可得 h <= 2log(n+1),又由于 h >= bh,所以 bh <= log(n+1)/2。红黑树的操作
4.颜色变换不能使一个节点和其父节点或子节点同为红色,否则会违反性质4(每个红色节点必须有两个黑色的子节点)。
4.如果 y 是红色,那么就存在双红问题,即 z 和 y 都是红色,违反了性质4。此时,需要根据 y 的叔叔节点(y 的父节点的兄弟节点)的颜色进行不同的处理:如果 y 的叔叔节点是红色,那么将 y 和其叔叔节点的颜色都变为黑色,将 y 的父节点的颜色变为红色,并将 y 的父节点作为新的 z 节点,继续向上调整。如果 y 的叔叔节点是黑色或 NIL,那么需要进行旋转操作。具体来说,分为以下四种情况:如果 z 是 y 的右孩子,并且 y 是其父节点的左孩子,那么对 y 进行左旋,然后交换 z 和 y 的角色。如果 z 是 y 的左孩子,并且 y 是其父节点的右孩子,那么对 y 进行右旋,然后交换 z 和 y 的角色。如果 z 是 y 的左孩子,并且 y 是其父节点的左孩子,那么对 y 的父节点进行右旋,然后将 y 的颜色变为黑色,将 y 的父节点(原来的祖父节点)的颜色变为红色,并结束调整。如果 z 是 y 的右孩子,并且 y 是其父节点的右孩子,那么对 y 的父节点进行左旋,然后将 y 的颜色变为黑色,将 y 的父节点(原来的祖父节点)的颜色变为红色,并结束调整。
3.如果 x 和 z 都是黑色,那么就存在过度黑问题,即 x 多了一个额外的黑色属性(因为替换了 z),导致违反了性质5。此时,需要根据 x 的兄弟节点(x 的父节点的另一个孩子)的颜色进行不同的处理:如果 x 的兄弟节点是红色,那么将 x 的兄弟节点的颜色变为黑色,将 x 的父节点的颜色变为红色,并对 x 的父节点进行左旋(如果 x 是左孩子)或右旋(如果 x 是右孩子),然后更新 x 的兄弟节点。如果 x 的兄弟节点是黑色,那么分为以下四种情况:如果 x 的兄弟节点的两个孩子都是黑色或 NIL,那么将 x 的兄弟节点的颜色变为红色,并将 x 的父节点作为新的 x 节点,继续向上调整。如果 x 是左孩子,并且 x 的兄弟节点的左孩子是红色,右孩子是黑色或 NIL,那么将 x 的兄弟节点的颜色变为红色,将 x 的兄弟节点的左孩子的颜色变为黑色,并对 x 的兄弟节点进行右旋,然后更新 x 的兄弟节点。如果 x 是右孩子,并且 x 的兄弟节点的右孩子是红色,左孩子是黑色或 NIL,那么将 x 的兄弟节点的颜色变为红色,将 x 的兄弟节点的右孩子的颜色变为黑色,并对 x 的兄弟节点进行左旋,然后更新 x 的兄弟节点。如果 x 是左孩子,并且 x 的兄弟节点的右孩子是红色,那么将 x 的父节点的颜色赋给 x 的兄弟节点,将 x 的父节点和 x 的兄弟节点的右孩子的颜色都变为黑色,并对 x 的父节点进行左旋,然后结束调整。如果 x 是右孩子,并且 x 的兄弟节点的左孩子是红色,那么将 x 的父节点的颜色赋给 x 的兄弟节点,将 x 的父节点和 x 的兄弟节点的左孩子的颜色都变为黑色,并对 x 的父节点进行右旋,然后结束调整。
// 堆代码 duidaima.com // 定义一个 Node 类,表示树中的每个节点 class Node { int key; // 键值 int color; // 颜色,0 表示黑色,1 表示红色 Node left; // 左孩子 Node right; // 右孩子 Node parent; // 父亲 // 构造方法,初始化键值和颜色 public Node(int key, int color) { this.key = key; this.color = color; this.left = null; this.right = null; this.parent = null; } } // 定义一个 RBTree 类,表示一棵红黑树 class RBTree { Node root; // 根节点 // 构造方法,初始化根节点为 null public RBTree() { this.root = null; } // 辅助方法,对某个节点进行左旋操作 public void leftRotate(Node x) { // 设 y 是 x 的右孩子 Node y = x.right; // 将 y 的左孩子设为 x 的右孩子,并将 y 的左孩子的父亲设为 x x.right = y.left; if (y.left != null) { y.left.parent = x; } // 将 x 的父亲设为 y 的父亲,并更新 y 的父亲的相应孩子指针 y.parent = x.parent; if (x.parent == null) { // 如果 x 是根节点,那么将 y 设为新的根节点 this.root = y; } else if (x == x.parent.left) { // 如果 x 是其父节点的左孩子,那么将 y 设为其父节点的左孩子 x.parent.left = y; } else { // 如果 x 是其父节点的右孩子,那么将 y 设为其父节点的右孩子 x.parent.right = y; } // 将 y 的左孩子设为 x,并将 x 的父亲设为 y y.left = x; x.parent = y; } // 辅助方法,对某个节点进行右旋操作 public void rightRotate(Node x) { // 设 y 是 x 的左孩子 Node y = x.left; // 将 y 的右孩子设为 x 的左孩子,并将 y 的右孩子的父亲设为 x x.left = y.right; if (y.right != null) { y.right.parent = x; } // 将 x 的父亲设为 y 的父亲,并更新 y 的父亲的相应孩子指针 y.parent = x.parent; if (x.parent == null) { // 如果 x 是根节点,那么将 y 设为新的根节点 this.root = y; } else if (x == x.parent.right) { // 如果 x 是其父节点的右孩子,那么将 y 设为其父节点的右孩子 x.parent.right = y; } else { // 如果 x 是其父节点的左孩子,那么将 y 设为其父节点的左孩子 x.parent.left = y; } // 将 y 的右孩子设为 x,并将 x 的父亲设为 y y.right = x; x.parent = y; } // 辅助方法,用 v 节点替换 u 节点 public void transplant(Node u, Node v) { if (u.parent == null) { // 如果 u 是根节点,那么将 v 设为新的根节点 this.root = v; } else if (u == u.parent.left) { // 如果 u 是其父节点的左孩子,那么将 v 设为其父节点的左孩子 u.parent.left = v; } else { // 如果 u 是其父节点的右孩子,那么将 v 设为其父节点的右孩子 u.parent.right = v; } if (v != null) { // 如果 v 不是 null,那么将 v 的父亲设为 u 的父亲 v.parent = u.parent; } } // 辅助方法,返回以某个节点为根的子树中最小键值的节点 public Node minimum(Node x) { while (x.left != null) { // 沿着左孩子指针一直向下,直到找到最左边的节点 x = x.left; } return x; } // 辅助方法,返回以某个节点为根的子树中最大键值的节点 public Node maximum(Node x) { while (x.right != null) { // 沿着右孩子指针一直向下,直到找到最右边的节点 x = x.right; } return x; } // 辅助方法,返回树中键值等于 key 的第一个找到的节点 public Node search(int key) { Node x = this.root; // 从根节点开始查找 while (x != null && x.key != key) { // 如果 x 不是 null,并且 x 的键值不等于 key,那么继续查找 if (key < x.key) { // 如果 key 小于 x 的键值,那么在 x 的左子树中查找 x = x.left; } else { // 如果 key 大于 x 的键值,那么在 x 的右子树中查找 x = x.right; } } return x; // 返回找到的节点或 null } // 向树中插入一个键值为 key 的节点 public void insert(int key) { Node z = new Node(key, 1); // 创建一个新节点 z,并将其颜色设为红色 Node y = null; // 初始化 y 为 null,用于记录 z 的父节点 Node x = this.root; // 初始化 x 为根节点,用于查找 z 的插入位置 while (x != null) { // 如果 x 不是 null,那么继续查找 y = x; // 将 y 设为当前的 x 节点 if (z.key < x.key) { // 如果 z 的键值小于 x 的键值,那么在 x 的左子树中查找 x = x.left; } else { // 如果 z 的键值大于或等于 x 的键值,那么在 x 的右子树中查找 x = x.right; } } z.parent = y; // 将 z 的父节点设为 y if (y == null) { // 如果 y 是 null,说明树是空的,那么将 z 设为根节点 this.root = z; } else if (z.key < y.key) { // 如果 z 的键值小于 y 的键值,那么将 z 设为 y 的左孩子 y.left = z; } else { // 如果 z 的键值大于或等于 y 的键值,那么将 z 设为 y 的右孩子 y.right = z; } insertFixup(z); // 调用插入修复方法,恢复红黑性质 } // 插入修复方法,用于恢复红黑树的性质 public void insertFixup(Node z) { // 当 z 的父节点存在且是红色时,需要进行调整 while (z.parent != null && z.parent.color == Color.RED) { // 判断 z 的父节点是其祖父节点的左孩子还是右孩子 if (z.parent == z.parent.parent.left) { // 如果是左孩子,那么获取 z 的叔叔节点(祖父节点的右孩子) Node y = z.parent.parent.right; // 根据叔叔节点的颜色分为三种情况 if (y != null && y.color == Color.RED) { // 如果叔叔节点是红色,那么将父节点和叔叔节点都变为黑色,将祖父节点变为红色,并将祖父节点作为新的 z 节点,继续向上调整 z.parent.color = Color.BLACK; y.color = Color.BLACK; z.parent.parent.color = Color.RED; z = z.parent.parent; } else { // 如果叔叔节点是黑色或 NIL,那么需要进行旋转操作 if (z == z.parent.right) { // 如果 z 是其父节点的右孩子,那么先对父节点进行左旋,然后交换 z 和其父节点的角色 z = z.parent; leftRotate(z); } // 如果 z 是其父节点的左孩子,那么对祖父节点进行右旋,然后将父节点变为黑色,将祖父节点变为红色,并结束调整 z.parent.color = Color.BLACK; z.parent.parent.color = Color.RED; rightRotate(z.parent.parent); } } else { // 如果是右孩子,那么获取 z 的叔叔节点(祖父节点的左孩子),与上面类似,只是左右对称 Node y = z.parent.parent.left; // 根据叔叔节点的颜色分为三种情况 if (y != null && y.color == Color.RED) { // 如果叔叔节点是红色,那么将父节点和叔叔节点都变为黑色,将祖父节点变为红色,并将祖父节点作为新的 z 节点,继续向上调整 z.parent.color = Color.BLACK; y.color = Color.BLACK; z.parent.parent.color = Color.RED; z = z.parent.parent; } else { // 如果叔叔节点是黑色或 NIL,那么需要进行旋转操作 if (z == z.parent.left) { // 如果 z 是其父节点的左孩子,那么先对父节点进行右旋,然后交换 z 和其父节点的角色 z = z.parent; rightRotate(z); } // 如果 z 是其父节点的右孩子,那么对祖父节点进行左旋,然后将父节点变为黑色,将祖父节点变为红色,并结束调整 z.parent.color = Color.BLACK; z.parent.parent.color = Color.RED; leftRotate(z.parent.parent); } } } // 最后确保根节点是黑色 this.root.color = Color.BLACK; } // 从树中删除一个键值为 key 的节点 public void delete(int key) { Node z = search(key); // 查找要删除的节点 if (z == null) { // 如果没有找到,直接返回 return; } Node x; // 用于记录替换后的新节点 Node y = z; // 用于记录要删除或移动的原始节点 Color yOriginalColor = y.color; // 用于记录 y 的原始颜色 if (z.left == null) { // 如果 z 没有左孩子,那么用其右孩子替换它 x = z.right; transplant(z, z.right); } else if (z.right == null) { // 如果 z 没有右孩子,那么用其左孩子替换它 x = z.left; transplant(z, z.left); } else { // 如果 z 有两个孩子,那么用其后继节点(右子树中最小的节点)替换它 y = minimum(z.right); // 找到 z 的后继节点 yOriginalColor = y.color; // 记录 y 的原始颜色 x = y.right; // 记录 y 的右孩子 if (y.parent == z) { // 如果 y 是 z 的右孩子,那么直接将 x 的父亲设为 y x.parent = y; } else { // 如果 y 不是 z 的右孩子,那么用 x 替换 y,并将 y 的右孩子设为 z 的右孩子 transplant(y, y.right); y.right = z.right; y.right.parent = y; } transplant(z, y); // 用 y 替换 z,并将 y 的左孩子设为 z 的左孩子 y.left = z.left; y.left.parent = y; y.color = z.color; // 将 y 的颜色设为 z 的颜色 } if (yOriginalColor == Color.BLACK) { // 如果 y 的原始颜色是黑色,那么就存在过度黑问题,需要调用删除修复方法,恢复红黑性质 deleteFixup(x); } } // 删除修复方法,用于恢复红黑性质 public void deleteFixup(Node x) { // 当 x 不是根节点,并且是黑色时,需要进行调整 while (x != this.root && x.color == Color.BLACK) { // 判断 x 是其父节点的左孩子还是右孩子 if (x == x.parent.left) { // 如果是左孩子,那么获取 x 的兄弟节点(父节点的右孩子) Node w = x.parent.right; // 根据兄弟节点的颜色分为四种情况 if (w.color == Color.RED) { // 如果兄弟节点是红色,那么将兄弟节点变为黑色,将父节点变为红色,并对父节点进行左旋,然后更新兄弟节点 w.color = Color.BLACK; x.parent.color = Color.RED; leftRotate(x.parent); w = x.parent.right; } if (w.left.color == Color.BLACK && w.right.color == Color.BLACK) { // 如果兄弟节点的两个孩子都是黑色或 NIL,那么将兄弟节点变为红色,并将父节点作为新的 x 节点,继续向上调整 w.color = Color.RED; x = x.parent; } else { // 如果兄弟节点的两个孩子不都是黑色或 NIL,那么需要进行旋转操作 if (w.right.color == Color.BLACK) { // 如果兄弟节点的右孩子是黑色或 NIL,那么将兄弟节点变为红色,将兄弟节点的左孩子变为黑色,并对兄弟节点进行右旋,然后更新兄弟节点 w.color = Color.RED; w.left.color = Color.BLACK; rightRotate(w); w = x.parent.right; } // 如果兄弟节点的右孩子是红色,那么将父节点的颜色赋给兄弟节点,将父节点和兄弟节点的右孩子都变为黑色,并对父节点进行左旋,然后结束调整 w.color = x.parent.color; x.parent.color = Color.BLACK; w.right.color = Color.BLACK; leftRotate(x.parent); x = this.root; // 将 x 设为根节点,结束循环 } } else { // 如果是右孩子,那么获取 x 的兄弟节点(父节点的左孩子),与上面类似,只是左右对称 Node w = x.parent.left; // 根据兄弟节点的颜色分为四种情况 if (w.color == Color.RED) { // 如果兄弟节点是红色,那么将兄弟节点变为黑色,将父节点变为红色,并对父节点进行右旋,然后更新兄弟节点 w.color = Color.BLACK; x.parent.color = Color.RED; rightRotate(x.parent); w = x.parent.left; } if (w.right.color == Color.BLACK && w.left.color == Color.BLACK) { // 如果兄弟节点的两个孩子都是黑色或 NIL,那么将兄弟节点变为红色,并将父节点作为新的 x 节点,继续向上调整 w.color = Color.RED; x = x.parent; } else { // 如果兄弟节点的两个孩子不都是黑色或 NIL,那么需要进行旋转操作 if (w.left.color == Color.BLACK) { // 如果兄弟节点的左孩子是黑色或 NIL,那么将兄弟节点变为红色,将兄弟节点的右孩子变为黑色,并对兄弟节点进行左旋,然后更新兄弟节点 w.color = Color.RED; w.right.color = Color.BLACK; leftRotate(w); w = x.parent.left; } // 如果兄弟节点的左孩子是红色,那么将父节点的颜色赋给兄弟节点,将父节点和兄弟节点的左孩子都变为黑色,并对父节点进行右旋,然后结束调整 w.color = x.parent.color; x.parent.color = Color.BLACK; w.left.color = Color.BLACK; rightRotate(x.parent); x = this.root; // 将 x 设为根节点,结束循环 } } } // 最后确保根节点是黑色 this.root.color = Color.BLACK; }