概念

红黑二叉树的背后基本思想是用标准二叉树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。
image.png

红链接

将两个2-结点链接起来构成一个3-结点

黑链接

普通2-结点

定义

  • 红链接均为左链接;
  • 没有任何一个结点同时和两条红链接相连;
  • 该树是完美黑色平衡的,即任意空链接到根根节点的路径上的黑连接数相同。

image.png

时间复杂度

最差2lgN,平均lgN
image.png

操作

旋转

某些操作会导致出现红色右链接或者两条连续的红链接,旋转会改变红链接的指向。

作用

保证红黑树的有序性和完美平衡性

左旋转

将一条红色右链接转换为左连接1595176030278-8f0bb847-47d1-4550-8949-fa7b9a313434.png

  1. Node rotateLeft(Node h) { // 图中E所指向的结点
  2. Node x = h.right;
  3. h.right = x.left; // 将右链接的左子结点移动到左链接的右结点
  4. x.left = h; // 连接起来
  5. x.color = h.color; // 置换颜色
  6. h.color = RED; // 必定为红色
  7. x.N = h.N; // 只是发生了旋转,x的节点个数并没有变化
  8. h.N = 1 + size(h.left) + size(h.right); // 左链接个节点数要重新计算
  9. return x;
  10. }

右旋转

将一个红色左连接转换为一个红色右链接

  1. Node rotateRight(Node h) {
  2. Node x = h.left;
  3. h.left = x.right;
  4. x.right = h;
  5. x.color = h.color;
  6. h.color = RED;
  7. x.N = h.N;
  8. h.N = 1 + size(h.left) + size(h.right);
  9. return x;
  10. }

颜色置换

转换一个节点的两个红色子节点颜色,并且将父节点的颜色由黑变红
image.png

插入

向单个2-结点插入新键

根据新结点老结点大小做对比,按以下两种情况插入,最终结果与3-结点等效

  1. 新结点小于老结点:直接新增一个红链接,即插入左子结点,设置结点红色
  2. 新结点大于老结点:插入之后马上进行左旋转

image.png

向树底部的2-结点插入新键

情况与上面类似,新键小于老键就立即新增,新键大于老键则需要在新增后进行左旋转。

向一颗双键树(即一个3-结点)中插入新键

存在三种情况:

  1. 新键大于原树中的两个键
  2. 新键小于原树中的两个键
  3. 新建处于原树中两个键之间

image.png
插入完成之后需要将根节点都设为黑链接,结点本身变成一个3-结点,所以需要变成红链接

向树底部的3-结点插入新键

除了讨论上述三种情况。指向新结点的链接可能是3-结点的
右链接(只转换颜色),
左连接(右旋转再换颜色),
中链接(先左旋转下层链接然后右旋转上层链接,最后转换颜色)

image.png
旋转条件:

  1. 如果右字节点是红色,左字节点是黑色,需要进行左旋;
  2. 如果左节点是红色,并且他的左子节点也是红色,进行右旋;
  3. 如果左右子节点都为红色,则进行颜色转换
    1. void flipColors(Node h) {
    2. h.color = RED;
    3. h.left.color = BLACK;
    4. h.right.color = BLACK;
    5. }
    ```java private static final boolean RED = true; private static final boolean BLACK = false;

private class Node { Key key; // 键 Value val // 相关联的值 Node left, right; // 左右子树 int N; // 这棵子树中的结点总数 boolean color; // 由其父节点指向它的链接的颜色

  1. Node(Key key, Value val, int N, boolean color) {
  2. this.key = key;
  3. this.val = val;
  4. this.N = N;
  5. this.color = color;
  6. }

}

private boolean isRed(Node x) { if (x == null) return false; return x.color == RED; }

  1. ```java
  2. private static final boolean RED = true;
  3. private static final boolean BLACK = false;
  4. public class RedBlackBST<Key extends Comparable<Key>, Value> {
  5. private Node root;
  6. private class Node
  7. private boolean isRed(Node h)
  8. private Node rotateLeft(Node h)
  9. private Node rotateRight(Node h)
  10. private void filpColors(Node h)
  11. private int Size()
  12. private void put(Key key, Value val) {
  13. // 查找key,找到则更新,否则创建新节点
  14. root = put(root, key, val);
  15. root.color = BLACK;
  16. }
  17. private void put(Node h, Key key, Value val) {
  18. if (h == null) { // 执行插入操作
  19. return new Node(key, value, RED);
  20. }
  21. int cmp = key.compareTo(h.key);
  22. if cop < 0 h.left = put(h.left, key, val);
  23. else if com > 0 h.right = put(h.right, key, val);
  24. else h.val = val; // 可以啥也不做
  25. // 进行旋转操作
  26. if (isRed(h.right) && !isRed(h.left)) rotateLeft(h); // 右字节点是红色,左字节点是黑色,需要进行左旋
  27. if (isRed(h.left) && isRed(h.left.left)) rotateRight(h); // 左节点是红色,并且他的左子节点也是红色,进行右旋
  28. if (isRed(h.right) && isRed(h.left)) filpColors(h); // 左右子节点都为红色,则进行颜色转换
  29. h.N = size(h.left) + size(h.right) + 1;
  30. return h;
  31. }
  32. }

删除

删除最小键

从树的底部的3-结点中删除很简单。
2-结点中删除会破坏树的平衡性,要沿着左链接向下进行变换,确保当前结点不是2-结点(3- or 4-)。
如果是2-结点且它的两个子节点都是2-结点,直接将三个结点变成4-结点,
否则需要保证根节点的左子结点不是2-结点。在沿着左链接向下的过程中保证一下情况之一成立:

  • 如果当前结点的左子节点不是2-结点,完成;
  • 如果当前结点的左子节点是2-结点而它的亲兄弟结点不是2-结点,将左子结点的兄弟结点中的一个键移动到左子结点中;
  • 如果当前节点的左子结点和它的亲兄弟结点都是2-结点,将左子结点、父结点中的最小键和左子结点最近的兄弟结点合并为一个4-结点,使父亲点由3-结点变为2结点或者由4-结点变为3-结点

image.png