内容来自刘宇波老师算法与数据结构体系课
1、红黑树介绍
参考 2 - 3 树
| 1、每个节点或者是红色的,或者是黑色的 |
| 2、根节点是黑色的 |
| 3、每一个叶子节点(最后的空节点)是黑色的 |
| 4、如果一个节点是红色的,那么他的孩子节点都是黑色的 |
| 5、从一个节点到任意叶子节点,经过的黑色节点是一样的 |
| |
| 红黑树是保持 "黑平衡" 的二叉树 |
| 严格意义上,不是平衡二叉树,最大高度:2 * logN |
| 红黑树添加和删除比 AVL 树快,查询比 AVL 树慢(它比 AVL 树更高) |



2、添加元素图示
2.1、添加元素时,需要保持根节点为黑色节点

2.2、插入 "二节点" 的右侧

2.3、插入 "三节点" 的右侧

2.4、插入 "三节点" 的左侧

2.5、插入 "三节点" 的中间

2.6、向红黑树中添加元素总体流程图示

3、辅助函数
| Node 新添加了一个成员变量 color,默认插入红色节点(新添加的元素一定是红色的) |
| |
| 辅助函数 |
| 1、判断节点 node 的颜色 |
| 2、保持根节点为黑色节点 |
| 3、左旋转 |
| 4、右旋转 |
| 5、颜色翻转 |
| |
| 将一个元素插入到一个 "二节点" 中,使其融合形成 "三节点" |
| 1、添加到左边 |
| 2、添加到右边:为了保证所有的红色节点都是左倾斜的,在这里需要通过左旋转来调整 |
| |
| 将一个元素插入到一个 "三节点" 中,使其变为临时的 "四节点" |
| 再拆解形成 3 个 "二节点",根节点继续向上,与它的父亲节点做融合 |
| 1、添加到右边:颜色翻转 |
| 2、添加到左边:右旋转、颜色翻转 |
| 3、添加到中间:左旋转、右旋转、颜色翻转 |
| private static final boolean RED = true; |
| private static final boolean BLACK = false; |
| |
| private class Node { |
| public K key; |
| public V value; |
| public Node left; |
| public Node right; |
| public boolean color; |
| |
| public Node(K key, V value) { |
| this.key = key; |
| this.value = value; |
| this.left = null; |
| this.right = null; |
| this.color = RED; |
| } |
| } |
| |
| |
| |
| |
| private boolean isRed(Node node) { |
| if (node == null) return BLACK; |
| return node.color; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private Node leftRotate(Node node) { |
| Node x = node.right; |
| |
| |
| node.right = x.left; |
| x.left = node; |
| |
| |
| x.color = node.color; |
| node.color = RED; |
| |
| return x; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private Node rightRotate(Node node) { |
| Node x = node.left; |
| |
| |
| node.left = x.right; |
| x.right = node; |
| |
| |
| x.color = node.color; |
| node.color = RED; |
| |
| return x; |
| } |
| |
| |
| |
| |
| private void flipColors(Node node) { |
| node.color = RED; |
| node.left.color = node.right.color = BLACK; |
| } |
| |
| |
| |
| |
| public void add(K key, V value) { |
| root = add(root, key, value); |
| root.color = BLACK; |
| } |
4、实现添加操作
| 添加操作 |
| 1、保持根节点为黑色节点 |
| 2、新添加的元素一定是红色的 |
| 3、保持 "黑平衡" |
| |
| 将一个元素插入到一个 "二节点" 中,使其融合形成 "三节点" |
| 1、添加到左边 |
| 2、添加到右边:为了保证所有的红色节点都是左倾斜的,在这里需要通过左旋转来调整 |
| |
| 将一个元素插入到一个 "三节点" 中,使其变为临时的 "四节点" |
| 再拆解形成 3 个 "二节点",根节点继续向上,与它的父亲节点做融合 |
| 1、添加到右边:颜色翻转 |
| 2、添加到左边:右旋转、颜色翻转 |
| 3、添加到中间:左旋转、右旋转、颜色翻转 |

| |
| |
| |
| public void add(K key, V value) { |
| root = add(root, key, value); |
| root.color = BLACK; |
| } |
| |
| |
| |
| |
| private Node add(Node node, K key, V value) { |
| if (node == null) { |
| size++; |
| return new Node(key, value); |
| } |
| |
| if (key.compareTo(node.key) < 0) node.left = add(node.left, key, value); |
| else if (key.compareTo(node.key) > 0) node.right = add(node.right, key, value); |
| else node.value = value; |
| |
| |
| if (isRed(node.right) && !isRed(node.left)) node = leftRotate(node); |
| if (isRed(node.left) && isRed(node.left.left)) node = rightRotate(node); |
| if (isRed(node.left) && isRed(node.right)) flipColors(node); |
| |
| return node; |
| } |
5、红黑树
| |
| |
| |
| |
| |
| public class RBTree<K extends Comparable<K>, V> { |
| |
| private static final boolean RED = true; |
| private static final boolean BLACK = false; |
| |
| private class Node { |
| public K key; |
| public V value; |
| public Node left; |
| public Node right; |
| public boolean color; |
| |
| public Node(K key, V value) { |
| this.key = key; |
| this.value = value; |
| this.left = null; |
| this.right = null; |
| this.color = RED; |
| } |
| } |
| |
| private Node root; |
| private int size; |
| |
| public RBTree() { |
| root = null; |
| size = 0; |
| } |
| |
| |
| |
| |
| private boolean isRed(Node node) { |
| if (node == null) return BLACK; |
| return node.color; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private Node leftRotate(Node node) { |
| Node x = node.right; |
| |
| |
| node.right = x.left; |
| x.left = node; |
| |
| |
| x.color = node.color; |
| node.color = RED; |
| |
| return x; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private Node rightRotate(Node node) { |
| Node x = node.left; |
| |
| |
| node.left = x.right; |
| x.right = node; |
| |
| |
| x.color = node.color; |
| node.color = RED; |
| |
| return x; |
| } |
| |
| |
| |
| |
| private void flipColors(Node node) { |
| node.color = RED; |
| node.left.color = node.right.color = BLACK; |
| } |
| |
| |
| |
| |
| private Node getNode(Node node, K key) { |
| if (node == null) return null; |
| |
| if (key.compareTo(node.key) == 0) return node; |
| if (key.compareTo(node.key) < 0) return getNode(node.left, key); |
| return getNode(node.right, key); |
| } |
| |
| |
| |
| |
| public void add(K key, V value) { |
| root = add(root, key, value); |
| root.color = BLACK; |
| } |
| |
| |
| |
| |
| private Node add(Node node, K key, V value) { |
| if (node == null) { |
| size++; |
| return new Node(key, value); |
| } |
| |
| if (key.compareTo(node.key) < 0) node.left = add(node.left, key, value); |
| else if (key.compareTo(node.key) > 0) node.right = add(node.right, key, value); |
| else node.value = value; |
| |
| |
| if (isRed(node.right) && !isRed(node.left)) node = leftRotate(node); |
| if (isRed(node.left) && isRed(node.left.left)) node = rightRotate(node); |
| if (isRed(node.left) && isRed(node.right)) flipColors(node); |
| |
| return node; |
| } |
| |
| |
| |
| |
| private Node minimum(Node node) { |
| if (node.left == null) return node; |
| return minimum(node.left); |
| } |
| |
| |
| |
| |
| private Node removeMin(Node node) { |
| if (node.left == null) { |
| Node rightNode = node.right; |
| node.right = null; |
| size--; |
| return rightNode; |
| } |
| |
| node.left = removeMin(node.left); |
| return node; |
| } |
| |
| public V remove(K key) { |
| Node node = getNode(root, key); |
| if (node != null) { |
| root = remove(root, key); |
| return node.value; |
| } |
| return null; |
| } |
| |
| |
| |
| |
| private Node remove(Node node, K key) { |
| if (node == null) return null; |
| |
| Node retNode; |
| if (key.compareTo(node.key) < 0) { |
| node.left = remove(node.left, key); |
| retNode = node; |
| } else if (key.compareTo(node.key) > 0) { |
| node.right = remove(node.right, key); |
| retNode = node; |
| } else { |
| if (node.left == null) { |
| Node rightNode = node.right; |
| node.right = null; |
| size--; |
| retNode = rightNode; |
| } else if (node.right == null) { |
| Node leftNode = node.left; |
| node.left = null; |
| size--; |
| retNode = leftNode; |
| } else { |
| Node successor = minimum(node.right); |
| successor.right = removeMin(node.right); |
| successor.left = node.left; |
| node.left = node.right = null; |
| retNode = successor; |
| } |
| } |
| |
| return retNode; |
| } |
| |
| public boolean contains(K key) { |
| return getNode(root, key) != null; |
| } |
| |
| public V get(K key) { |
| Node node = getNode(root, key); |
| return node != null ? node.value : null; |
| } |
| |
| public void set(K key, V newValue) { |
| Node node = getNode(root, key); |
| if (node != null) node.value = newValue; |
| else throw new IllegalArgumentException(key + " doesn't exist!"); |
| } |
| |
| public int getSize() { |
| return size; |
| } |
| |
| public boolean isEmpty() { |
| return size == 0; |
| } |
| } |
6、性能总结
| 1、二分搜索树 |
| 对于完全随机的数据,普通的二分搜索树很好用! |
| 缺点:极端情况退化成链表(或者高度不平衡) |
| |
| 2、AVL 树 |
| 对于查询较多的使用情况,AVL 树很好用! |
| |
| 3、红⿊树 |
| 红黑树牺牲了平衡性(2 * logN 的高度) |
| 统计性能更优(综合增删改查所有的操作) |
| |
| 4、Splay Tree 伸展树 |
| 另一种统计性能优秀的树结构 |
| 局部性原理:刚被访问的内容下次高概率被再次访问 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步