红黑树基本知识
1. 什么是红黑树?
想象一个纪律严明的军队方阵:
graph TD
7B((7黑)) --> 3B((3黑))
7B --> 10B((10黑))
3B --> 1R((1红))
3B --> 5R((5红))
10B --> 9R((9红))
10B --> 12R((12红))
style 7B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 1R fill:#f66,stroke:#333,stroke-width:2px
style 5R fill:#f66,stroke:#333,stroke-width:2px
style 9R fill:#f66,stroke:#333,stroke-width:2px
style 12R fill:#f66,stroke:#333,stroke-width:2px
红黑树的基本结构
class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
class Node {
K key;
V value;
Node left, right, parent;
boolean color; // true表示红色,false表示黑色
Node(K key, V value) {
this.key = key;
this.value = value;
this.color = RED; // 新节点默认为红色
}
}
private Node root;
}
2.为什么红黑树查询比较快?
让我用图解方式解释为什么红黑树的查询效率高。
1. 平衡性保证
graph TD
subgraph 平衡的红黑树
7B((7黑)) --> 3B((3黑))
7B --> 11B((11黑))
3B --> 1R((1红))
3B --> 5R((5红))
11B --> 9R((9红))
11B --> 13R((13红))
end
特点:
1. 从根到任何叶子的最长路径不会超过最短路径的2倍
2. 黑节点的分布保证了基本的平衡性
2. 对比普通二叉搜索树
graph TD
subgraph 不平衡的二叉搜索树
1B((1)) --> 2B((2))
2B --> 3B((3))
3B --> 4B((4))
4B --> 5B((5))
5B --> 6B((6))
end
查找6的过程:
- 普通二叉树:需要6步
- 红黑树:最多3步
3. 时间复杂度分析
对于n个节点的树:
普通二叉搜索树:
- 最好情况:O(logn)
- 最坏情况:O(n)
- 平均情况:O(logn)
红黑树:
- 最好情况:O(logn)
- 最坏情况:O(logn)
- 平均情况:O(logn)
4. 具体查找过程示例
假设查找值为9:
graph TD
subgraph 查找步骤
7B((7黑<br>步骤1)) --> 3B((3黑))
7B --> 11B((11黑<br>步骤2))
3B --> 1R((1红))
3B --> 5R((5红))
11B --> 9R((9红<br>步骤3))
11B --> 13R((13红))
end
style 7B fill:#f96,stroke:#333,stroke-width:2px
style 11B fill:#f96,stroke:#333,stroke-width:2px
style 9R fill:#f96,stroke:#333,stroke-width:2px
查找步骤:
- 比较7,向右
- 比较11,向左
- 找到9
5. 为什么快?
1. 保证的平衡性
graph TD
subgraph 高度保证
A[树高度] --> B[最多2logn]
B --> C[查找次数有上限]
end
2. 自动平衡
graph TD
subgraph 自平衡机制
A[插入/删除] --> B[旋转和变色]
B --> C[保持平衡]
C --> D[防止退化]
end
6. 实际应用中的性能
// TreeMap的查找操作
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
// 二分查找的过程
final Entry<K,V> getEntry(Object key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
7. 与其他数据结构比较
查找效率对比:
数组:
- 无序:O(n)
- 有序:O(logn)(二分查找)
哈希表:
- 平均:O(1)
- 最坏:O(n)
红黑树:
- 稳定O(logn)
8. 使用场景
适合的场景:
1. 需要保持数据有序
2. 需要稳定的查找性能
3. 数据量较大且经常变动
例如:
TreeMap<Integer, String> map = new TreeMap<>();
// 1. 自动保持键的顺序
// 2. 查找性能稳定
// 3. 适合范围查询
所以红黑树查询快的原因是:
- 保证了树的平衡性
- 限制了最大高度
- 提供了稳定的性能保证
- 自动维护平衡,防止性能退化
这就是为什么Java的TreeMap和TreeSet都选择使用红黑树作为底层实现!
2. 红黑树的五大规则
规则1:每个节点要么是红色,要么是黑色
就像军队中:
- 黑色:军官(指挥官)
- 红色:士兵(执行者)
// 检查节点颜色
private boolean isRed(Node node) {
if (node == null) return BLACK; // 空节点是黑色
return node.color == RED;
}
// 设置节点颜色
private void setColor(Node node, boolean color) {
if (node != null) {
node.color = color;
}
}
规则2:根节点必须是黑色
graph TD
7B((总指挥官)) --> 3B((指挥官))
7B --> 10B((指挥官))
style 7B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B fill:#333,stroke:#333,stroke-width:2px,color:#fff
// 插入后检查并修正根节点颜色
private void insert(K key, V value) {
root = insert(root, key, value);
root.color = BLACK; // 确保根节点为黑色
}
规则3:所有叶子节点(NIL)都是黑色
就像每个队伍的末端都由军官坐镇
// 在实现中,null表示NIL节点,默认为黑色
private boolean isNil(Node node) {
return node == null; // null节点就是黑色的NIL节点
}
规则4:如果一个节点是红色,则它的子节点必须是黑色
graph TD
3B((指挥官)) --> 1R((士兵))
3B --> 5R((士兵))
1R --> NB1((NIL))
1R --> NB2((NIL))
5R --> NB3((NIL))
5R --> NB4((NIL))
style 3B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 1R fill:#f66,stroke:#333,stroke-width:2px
style 5R fill:#f66,stroke:#333,stroke-width:2px
style NB1 fill:#ddd,stroke:#333,stroke-width:2px
style NB2 fill:#ddd,stroke:#333,stroke-width:2px
style NB3 fill:#ddd,stroke:#333,stroke-width:2px
style NB4 fill:#ddd,stroke:#333,stroke-width:2px
就像:
士兵(红色)不能直接管理其他士兵
必须由军官(黑色)来管理士兵
// 检查是否违反红色节点规则
private boolean isRedViolation(Node node) {
if (node == null) return false;
// 如果当前节点是红色,检查其子节点
if (isRed(node)) {
if (isRed(node.left) || isRed(node.right)) {
return true; // 违反规则
}
}
return false;
}
// 修正红色冲突
private void fixRedConflict(Node node) {
// 如果父节点和叔叔节点都是红色
if (isRed(node.parent) && isRed(getUncle(node))) {
// 变色操作
node.parent.color = BLACK;
getUncle(node).color = BLACK;
node.parent.parent.color = RED;
} else {
// 需要旋转操作
handleRotation(node);
}
}
规则5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
graph TD
7B((7黑)) --> 3B((3黑))
7B --> 10B((10黑))
3B --> 1R((1红))
3B --> 5R((5红))
1R --> NB1((NIL))
1R --> NB2((NIL))
5R --> NB3((NIL))
5R --> NB4((NIL))
style 7B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 1R fill:#f66,stroke:#333,stroke-width:2px
style 5R fill:#f66,stroke:#333,stroke-width:2px
style NB1 fill:#ddd,stroke:#333,stroke-width:2px
style NB2 fill:#ddd,stroke:#333,stroke-width:2px
style NB3 fill:#ddd,stroke:#333,stroke-width:2px
style NB4 fill:#ddd,stroke:#333,stroke-width:2px
就像:
从总指挥到每个末端队伍
必须经过相同数量的军官
保证命令传达的层级一致
// 验证黑色节点数量是否平衡
private int validateBlackHeight(Node node) {
if (node == null) return 1; // NIL节点算作1个黑色节点
int leftHeight = validateBlackHeight(node.left);
int rightHeight = validateBlackHeight(node.right);
// 左右子树的黑色高度必须相同
if (leftHeight != rightHeight) {
throw new RuntimeException("Invalid Red-Black tree");
}
// 返回当前路径的黑色节点数
return leftHeight + (isRed(node) ? 0 : 1);
}
3. 红黑树的操作
插入新节点
graph TD
subgraph 插入前
7B1((7黑)) --> 3B1((3黑))
7B1 --> 10B1((10黑))
end
subgraph 插入后
7B2((7黑)) --> 3B2((3黑))
7B2 --> 10B2((10黑))
3B2 --> 4R((4红))
end
style 7B1 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3B1 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B1 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 7B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 4R fill:#f66,stroke:#333,stroke-width:2px
就像:
新人加入时先作为士兵(红色)
然后通过调整确保军队结构稳定
public void put(K key, V value) {
// 1. 标准BST插入
root = put(root, key, value);
// 2. 确保根节点为黑色
root.color = BLACK;
}
private Node put(Node node, K key, V value) {
// 1. 标准BST插入
if (node == null) {
return new Node(key, value); // 新节点默认为红色
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
node.left = put(node.left, key, value);
} else if (cmp > 0) {
node.right = put(node.right, key, value);
} else {
node.value = value;
return node;
}
// 2. 修正红黑树性质
// 处理右边是红色,左边是黑色的情况
if (isRed(node.right) && !isRed(node.left)) {
node = rotateLeft(node);
}
// 处理连续的红色节点
if (isRed(node.left) && isRed(node.left.left)) {
node = rotateRight(node);
}
// 处理两个子节点都是红色的情况
if (isRed(node.left) && isRed(node.right)) {
flipColors(node);
}
return node;
}
旋转操作
左旋操作详解
1. 初始状态
graph TD
3B((3黑)) --> 1R((1红))
3B --> 5R((5红))
5R --> 4R((4红))
5R --> 7R((7红))
style 3B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 1R fill:#f66,stroke:#333,stroke-width:2px
style 5R fill:#f66,stroke:#333,stroke-width:2px
style 4R fill:#f66,stroke:#333,stroke-width:2px
style 7R fill:#f66,stroke:#333,stroke-width:2px
2. 左旋过程
就像跳舞时的转圈:
graph TD
subgraph 步骤1-初始
3B1((3黑)) --> 1R1((1红))
3B1 --> 5R1((5红))
5R1 --> 4R1((4红))
5R1 --> 7R1((7红))
end
subgraph 步骤2-旋转中
5R2((5红)) --> 3B2((3黑))
5R2 --> 7R2((7红))
3B2 --> 1R2((1红))
3B2 --> 4R2((4红))
end
style 3B1 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 1R1 fill:#f66,stroke:#333,stroke-width:2px
style 5R1 fill:#f66,stroke:#333,stroke-width:2px
style 4R1 fill:#f66,stroke:#333,stroke-width:2px
style 7R1 fill:#f66,stroke:#333,stroke-width:2px
style 5R2 fill:#f66,stroke:#333,stroke-width:2px
style 3B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 7R2 fill:#f66,stroke:#333,stroke-width:2px
style 1R2 fill:#f66,stroke:#333,stroke-width:2px
style 4R2 fill:#f66,stroke:#333,stroke-width:2px
// 左旋操作
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
return x;
}
// 右旋操作
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
return x;
}
// 颜色翻转
private void flipColors(Node h) {
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
}
左旋的具体步骤
- 节点位置变化:
- 原来的3(黑)是根节点
- 5(红)是3的右子节点
- 左旋后5变成新的根节点
- 3变成5的左子节点
- 子树的处理:
- 4从5的左子树变成3的右子树
- 7保持为5的右子树
- 1保持为3的左子树
形象比喻
想象一个跳芭蕾舞的动作:
1. 3和5像是两个舞者
2. 3向左转
3. 5升到上面
4. 4跟着3走
或者像玩跷跷板:
- 3往下沉
- 5往上升
- 中间的4跟着3走
旋转操作规则
1. 旋转的触发条件
graph TD
subgraph 情况1-连续的红节点
7B1((7黑)) --> 5R1((5红))
5R1 --> 3R1((3红))
end
subgraph 情况2-不平衡的黑高度
7B2((7黑)) --> 5B2((5黑))
7B2 --> 9R2((9红))
5B2 --> 3R2((3红))
5B2 --> 6R2((6红))
end
style 7B1 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5R1 fill:#f66,stroke:#333,stroke-width:2px
style 3R1 fill:#f66,stroke:#333,stroke-width:2px
style 7B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 9R2 fill:#f66,stroke:#333,stroke-width:2px
style 3R2 fill:#f66,stroke:#333,stroke-width:2px
style 6R2 fill:#f66,stroke:#333,stroke-width:2px
主要在以下情况需要旋转:
- 插入后出现连续的红节点
- 删除后破坏黑色平衡
2. 旋转的基本规则
左旋规则:
graph TD
subgraph 左旋前
P((P)) --> A((A))
P --> R((R))
R --> B((B))
R --> C((C))
end
subgraph 左旋后
R2((R)) --> P2((P))
R2 --> C2((C))
P2 --> A2((A))
P2 --> B2((B))
end
步骤:
- R成为新的根节点
- P成为R的左子节点
- B从R的左子树变成P的右子树
右旋规则:
graph TD
subgraph 右旋前
P((P)) --> L((L))
P --> C((C))
L --> A((A))
L --> B((B))
end
subgraph 右旋后
L2((L)) --> A2((A))
L2 --> P2((P))
P2 --> B2((B))
P2 --> C2((C))
end
步骤:
- L成为新的根节点
- P成为L的右子节点
- B从L的右子树变成P的左子树
3. 具体场景和处理方法
场景1:插入节点后的调整
graph TD
subgraph 插入前
10B((10黑)) --> 5B((5黑))
10B --> 15B((15黑))
end
subgraph 插入后
10B2((10黑)) --> 5B2((5黑))
10B2 --> 15B2((15黑))
5B2 --> 3R((3红))
end
style 10B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 15B fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 15B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3R fill:#f66,stroke:#333,stroke-width:2px
处理步骤:
- 新节点总是插入为红色
- 检查是否违反红黑树规则
- 根据情况选择旋转或变色
4. 旋转的判断口诀
1. 左左情况:右旋一次
2. 右右情况:左旋一次
3. 左右情况:先左旋后右旋
4. 右左情况:先右旋后左旋
5. 代码实现模板
private void leftRotate(Node node) {
Node right = node.right;
node.right = right.left;
if (right.left != null)
right.left.parent = node;
right.parent = node.parent;
if (node.parent == null)
root = right;
else if (node == node.parent.left)
node.parent.left = right;
else
node.parent.right = right;
right.left = node;
node.parent = right;
}
5. 实际运行示例
插入序列 [10, 5, 15, 3, 7]:
graph TD
subgraph 步骤1
10B1((10黑))
end
subgraph 步骤2
10B2((10黑)) --> 5R2((5红))
end
subgraph 步骤3
10B3((10黑)) --> 5R3((5红))
10B3 --> 15R3((15红))
end
subgraph 步骤4
10B4((10黑)) --> 5B4((5黑))
10B4 --> 15B4((15黑))
5B4 --> 3R4((3红))
end
style 10B1 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 10B2 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5R2 fill:#f66,stroke:#333,stroke-width:2px
style 10B3 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5R3 fill:#f66,stroke:#333,stroke-width:2px
style 15R3 fill:#f66,stroke:#333,stroke-width:2px
style 10B4 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 5B4 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 15B4 fill:#333,stroke:#333,stroke-width:2px,color:#fff
style 3R4 fill:#f66,stroke:#333,stroke-width:2px
6. 实际应用记忆方法
想象跳舞的动作:
左旋:向左转圈
右旋:向右转圈
双旋:转两次圈
或者像魔方的旋转:
左旋:左面向上转
右旋:右面向上转
记住这些规则后,就能更容易理解和实现红黑树的平衡操作了!
4. 红黑树的应用
- Java中的TreeMap和TreeSet
TreeMap<Integer, String> map = new TreeMap<>();
// 自动保持有序,并保持平衡
- Linux内核中的进程调度
使用红黑树管理进程
保证快速查找和调度
5. 为什么要用红黑树?
优点:
1. 查找速度快(类似二分查找)
2. 插入删除后自动平衡
3. 最坏情况性能有保证
就像一支纪律严明的军队:
- 层级分明(黑红交替)
- 命令传达快速(查找效率高)
- 调整灵活(自动平衡)
这就是红黑树,一个看似复杂但实际非常实用的数据结构。它就像一支训练有素的军队,通过严格的规则保持高效和平衡!
记忆相关内容
1. 红黑树的五大特性:
想象一个规范的公司组织:
1. 每个员工要么穿红色工作服,要么穿黑色工作服(节点非红即黑)
2. 老板必须穿黑色西装(根节点必须是黑色)
3. 新员工(叶子NIL节点)必须穿黑色工作服
4. 穿红色工作服的员工的直接上级必须穿黑色(红节点的父节点必须是黑色)
5. 从老板到任何一个普通员工,路径上的黑衣人数量必须相同(每条路径上黑节点数量相同)
2. 为什么要有这些规则?
平衡的关键:
- 就像公司管理:
* 不能两个红色员工直接上下级(避免连续红节点)
* 黑色数量平衡(确保树的高度平衡)
* 最长路径不会超过最短路径的2倍
3. 插入新节点的处理:
新员工入职规则:
1. 新人先穿红色工作服(新节点默认红色)
2. 如果发现问题,有三种处理方式:
a. 换色:
直接上级和同级都换色(父节点和叔叔节点变黑,爷爷变红)
b. 左旋:
类似公司调整:
爸爸 -> 我 -> 孙子
变成:
我 -> 爸爸 -> 孙子
c. 右旋:
反方向的调整:
爸爸 -> 我 -> 孙子
变成:
孙子 -> 我 -> 爸爸
4. 应用场景:
1. Java的TreeMap和TreeSet的底层实现
2. Linux的进程调度
3. 数据库的索引结构
5. 性能特点:
时间复杂度:
- 查找:O(logN)
- 插入:O(logN)
- 删除:O(logN)
就像在一个组织良好的公司:
- 查找任何员工都很快(树的高度被限制)
- 新员工入职流程规范(插入操作)
- 员工离职处理有序(删除操作)
6. 面试回答技巧:
1. 先说应用场景(TreeMap等)
2. 再说五大特性(用公司组织类比)
3. 然后说优势(平衡性好,性能稳定)
4. 最后说操作(插入、删除的处理方式)
记住关键词:
- 红黑两色
- 根黑叶黑
- 红父必黑
- 黑色平衡
- 左旋右旋
7. 相比AVL树的优势:
就像公司管理:
- AVL树太严格(要求左右子树高度差不超过1)
像是要求每个部门人数必须完全平衡
- 红黑树更灵活
像是允许部门间有一定的人数差异
但保证不会差距过大
记住这个比喻:
红黑树就像一个组织良好的公司:
- 有明确的着装规定(颜色规则)
- 有严格的等级制度(树的结构)
- 有灵活的调整机制(旋转和变色)
- 保持整体平衡(性能稳定)
这样回答:
- 简单易懂
- 容易记忆
- 突出重点
- 展示理解深度
相关题目
1. TreeMap的应用题
【题目732】:我的日程安排表 III
class MyCalendarThree {
// 使用TreeMap记录时间点的预订次数变化
TreeMap<Integer, Integer> delta;
public MyCalendarThree() {
delta = new TreeMap<>();
}
public int book(int start, int end) {
// 记录时间点的预订次数变化
delta.put(start, delta.getOrDefault(start, 0) + 1);
delta.put(end, delta.getOrDefault(end, 0) - 1);
int active = 0, ans = 0;
// 统计最大重叠预订数
for (int d : delta.values()) {
active += d;
ans = Math.max(ans, active);
}
return ans;
}
}
图解过程:
graph LR
A[时间轴] --> B[10:00 +1]
B --> C[20:00 -1]
C --> D[50:00 +1]
D --> E[60:00 -1]
style A fill:#f96,stroke:#333,stroke-width:2px
2. TreeSet的应用题
【题目220】:存在重复元素 III
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
TreeSet<Long> set = new TreeSet<>();
for (int i = 0; i < nums.length; i++) {
// 查找大于等于当前值-t的最小值
Long ceiling = set.ceiling((long) nums[i] - t);
// 检查是否找到符合条件的值
if (ceiling != null && ceiling <= (long) nums[i] + t)
return true;
// 将当前值加入集合
set.add((long) nums[i]);
// 维护滑动窗口大小为k
if (i >= k)
set.remove((long) nums[i - k]);
}
return false;
}
滑动窗口示意图:
graph LR
A((1)) --> B((4))
B --> C((9))
C --> D((13))
style A fill:#f96,stroke:#333,stroke-width:2px
style B fill:#f96,stroke:#333,stroke-width:2px
style C fill:#f96,stroke:#333,stroke-width:2px
3. 平衡树特性题
【题目1382】:将二叉搜索树变平衡
public TreeNode balanceBST(TreeNode root) {
// 中序遍历获取有序数组
List<Integer> values = new ArrayList<>();
inorder(root, values);
// 将有序数组转换为平衡二叉树
return buildTree(values, 0, values.size() - 1);
}
private void inorder(TreeNode node, List<Integer> values) {
if (node == null) return;
inorder(node.left, values);
values.add(node.val);
inorder(node.right, values);
}
private TreeNode buildTree(List<Integer> values, int start, int end) {
if (start > end) return null;
int mid = (start + end) / 2;
TreeNode node = new TreeNode(values.get(mid));
node.left = buildTree(values, start, mid - 1);
node.right = buildTree(values, mid + 1, end);
return node;
}
转换过程示意图:
graph TD
subgraph 转换前
4A((4)) --> 2A((2))
4A --> 6A((6))
2A --> 1A((1))
2A --> 3A((3))
6A --> 5A((5))
6A --> 7A((7))
end
subgraph 转换后
4B((4)) --> 2B((2))
4B --> 6B((6))
2B --> 1B((1))
2B --> 3B((3))
6B --> 5B((5))
6B --> 7B((7))
end
4. 实际应用建议
- 掌握TreeMap/TreeSet的使用
TreeMap<Integer, Integer> map = new TreeMap<>();
// 常用方法
map.floorKey(key); // 小于等于key的最大键
map.ceilingKey(key); // 大于等于key的最小键
map.firstKey(); // 最小键
map.lastKey(); // 最大键
- 理解平衡树的特性
- 查找/插入/删除的时间复杂度都是O(logN)
- 中序遍历可以得到有序序列
- 平衡因子的概念
- 实际面试中的重点
- 理解红黑树的应用场景
- 会使用Java中的TreeMap/TreeSet
- 理解平衡树的基本特性
- 不需要手写红黑树的实现
这些题目主要考察对红黑树特性的理解和应用,而不是具体实现。在面试中,重点掌握TreeMap和TreeSet的使用,以及平衡树的基本特性就足够了。