我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
一、架构
二、特性
2.1 二叉查找树的特点
任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
2.2 红黑树的特点
- 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。
- 节点要么黑色,要么红色
- 根节点是黑的
- 叶子节点也是黑的 [注意:这里叶子节点,是指为空的叶子节点!]
- 如果一个节点是红色的,则它的儿子必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。(确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。)
总结: 非黑即红,头尾是黑,红儿是黑,各路同黑。
2.3 红黑树的基本操作
- 添加
- 删除
- 旋转。
- 为什么要旋转?
- 旋转的目的是让树保持红黑树的特性。
- 添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。
- 旋转包括两种:左旋 和 右旋。
2.3.1 【左旋】
2.3.1.1 图示与步骤
- 先检查 P 是不是空,并且 P 的右边有东西
- P 的右指针指向了 Y 的左节点
- 原先 Y 的左节点换爸爸
- Y 的爸爸换成了 P 的爸爸。由于 P 的爸爸比较顶部,所以假如原先 root (root 指向的节点的爸爸是 null) 指向了 P 话,那么此时 root 改为指向了 Y ,并且由于红黑树性质“头尾是黑”,此时 Y 的节点的 red 肯定是 false
- 顶部爸爸换儿子(原先儿子是 P, 现在要变成 Y),先判断原先孩子P到底是左孩子,还是右孩子,然后再替换
- Y 的左孩子变成了 P
- P 的爸爸变成了 Y
2.3.1.2 代码(HashMap1.8)
- HashMap中红黑树的代码(jdk1.8)
- java.util.HashMap.TreeNode#rotateLeft
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> y, pp, yl;
// 1. 先检查 P 是不是空,并且 P的右边有东西
if (p != null && (y = p.right) != null) {
// 2. P 的右指针指向了 Y 的左节点
if ((yl = p.right = y.left) != null)
// 3.原先 Y 的左节点换爸爸
yl.parent = p;
// 4. Y 的爸爸换成了 P 的爸爸。
if ((pp = y.parent = p.parent) == null)
// 假如 “原先 root 指向 P”,进入此代码
(root = y).red = false;
// 5. 顶部爸爸换儿子(原先儿子是 P, 现在要变成 Y)
// 先判断原先孩子P到底是左孩子,还是右孩子,然后再替换
else if (pp.left == p)
// 原先是左孩子
pp.left = y;
else
// 原先是右孩子
pp.right = y;
// 6. Y 的左孩子变成了 P
y.left = p;
// 7. P 的爸爸变成了 Y
p.parent = y;
}
return root;
}
2.3.2 【右旋】
2.3.2.1 图示与步骤
- 先检查 P 是否为空,并且检查 P 是否有左孩子
- P 的左指针指向了 X 的右孩子
- 原先 X 的右节点换爸爸
- X 的爸爸换成了 P 的爸爸。由于 P 的爸爸比较顶部,所以假如原先 root (root 指向的节点的爸爸是 null) 指向了 P 话,那么此时 root 改为指向了 Y ,并且由于红黑树性质“头尾是黑”,此时 Y 的节点的 red 肯定是 false
- 顶部爸爸换儿子(原先儿子是 P, 现在要变成 Y),先判断原先孩子P到底是左孩子,还是右孩子,然后再替换
- X 的右儿子变成 P
- P 的爸爸变成了 X
2.3.2.2 代码(HashMap1.8)
- HashMap中红黑树的代码(jdk1.8)
- java.util.HashMap.TreeNode#rotateRight
static <K,V> HashMap.TreeNode<K,V> rotateRight(HashMap.TreeNode<K,V> root,
HashMap.TreeNode<K,V> p) {
HashMap.TreeNode<K,V> x, pp, lr;
// 1. 先检查 P 是否为空,并且检查 P 是否有左孩子
if (p != null && (x = p.left) != null) {
// 2. P 的左指针指向了 X 的右孩子
if ((lr = p.left = x.right) != null)
// 3. 原先 X 的右节点换爸爸
lr.parent = p;
// 4. X 的爸爸换成了 P 的爸爸
if ((pp = x.parent = p.parent) == null)
// 假如 “原先 root 指向 P”,进入此代码
(root = x).red = false;
else if (pp.right == p)
// 原先是右孩子
pp.right = x;
else
// 原先是左孩子
pp.left = x;
// 6. X 的右儿子变成 P
x.right =