一. 2-3树与红黑树
1. 2-3树
2-3树的特点:
- 学习2-3树,不仅对于理解红黑树有帮助,对于理解B类树也有巨大帮助
- 2-3树满足二分搜索树的基本性质
- 在二分搜索树性质的基础上,2-3树的节点可以存放一个元素或者两个元素,存放两个元素的节点具有三棵子树,且在该节点处且满足:左子节点<b<中间子节点<c<右子节点
- 每个节点有2个或者3个孩子,即2-3树名字的由来(2节点不允许有只1个孩子,3节点不允许只有1个孩子或只有2个孩子)
- 2-3树是一棵绝对平衡的树,即从根节点到任意一个叶子节点,所经过的节点数一定是相同的
2-3树维持绝对平衡的机制:
- 2-3树在添加节点时,绝对不会将节点添加到一个空位置,而是通过与最终找到的叶子节点作融合和分裂操作来保持树的绝对平衡性
- 添加节点过程中可能会出现4节点的中间状态,但通过分裂,将保持树中最终只包含2节点和3节点
- 依次添加元素 42, 37, 12, 18, 6, 5, 11,2-3树维持绝对平衡的演变过程:
2. 红黑树
- 《算法导论》中红黑树的定义:
- 每个节点或者是红色,或者是黑色
- 根节点是黑色的
- 每一个叶子节点(最后的空节点)是黑色的
- 如果一个节点是红色的,那么它的孩子节点都是黑色的
- 从任意一个节点到叶子节点,经过的黑色节点的数量是一样的(红黑树是一棵 “黑色绝对平衡” 的树)
3. 红黑树与2-3树
红黑树中红色节点的由来:
- 红黑树中的黑色节点就相当于2-3树中的2节点
- 2-3树中的3节点在红黑树中可以看作是一个黑节点和一个红节点,红节点是黑节点的子节点
- 红色节点和它的父亲节点一起表示在2-3树中的3节点,这样,在红黑树中将不存在存储两个数据的3节点
- 由红色节点的定义可知,所有的红色节点都是向左倾斜的
将一棵2-3树转化为红黑树:
红黑树的特点:
- 红黑树是一棵“黑色节点绝对平衡”的二叉树
- 严格意义上,红黑树不是平衡二叉树,其某个节点的左右子树高度差可能是大于1的
- 若有n个节点,则红黑树最差情况下,其高度为2logn, 不考虑常数项,在红黑树上进行增删改查是,其复杂度为O(logn)
- 红黑树的添加和删除操作性能优于AVL树,但查找操作略次于AVL树
二. 红黑树添加元素的实现
2.1 添加节点前的辅佐过程
1. 保持根节点为黑色以及左旋转(在一个2节点中融入一个新节点):
保持根节点为黑色
- 根据定义,红黑树的根节点一定为黑色,因为红色节点要与其父亲节点形成一个3节点,所以红色节点一定有父亲节点
public void add(K key, V value) {
root = add(root, key, value);
root.color = BLACK; //最终保持根节点为黑色
}
左旋转(相当于2-3树中将一个新节点融合到一个2节点后形成一个3节点的过程)
情况1:当新插入的节点在根节点的左侧时,直接插入即可
情况2: 当新插入的节点在根节点的右侧时
- 当插入的节点在根节点的右侧时,由于新节点初始化为红色,而红黑树中红色节点必须是左倾斜的,所以需要将根节点相对于新插入的节点作左旋转,并做颜色修改
- 左旋转后的颜色修改:首先,将x的颜色设为node的颜色,因为旋转前以node作为根节点,而旋转后,x成为了根节点,这里要保持根节点颜色不变; 其次,将旋转后的node节点设为红色,这是因为新加入的节点x是和原根节点node形成了一个3节点,而3节点中的左节点一定是红色的,所以旋转后的node一定为红。注意,这里可能会出现x和node都为红色的情况,但这里只是添加节点的一个子过程,后续会有相应的处理。
- 左旋转只是保证新添加的节点x与原节点node是一个3节点
//辅助函数,将节点node进行左旋转,修改颜色,返回旋转后的根节点
private Node leftRotate(Node node){
Node x = node.right;
//左旋转
node.right = x.left;
x.left = node;
//维护颜色
x.color = node.color;
node.color = BLACK;
return x;
}
2. 颜色翻转和右旋转(在一个3节点中融入一个新节点):
情况1:颜色翻转(flipColors)过程(新添加的元素在最右侧):
- 颜色翻转相当于在2-3树中将一个新节点融合到一个3节点的右侧后形成一个临时的4节点的过程,临时的4节点左右子节点都是红色的
- 临时的4节点先会被拆分成三个2节点,此时,2节点的颜色变为为黑色
- 三个2节点中的根节点需要向上作融合操作,即需要将其置为红色
//颜色翻转,在3节点上融合新节点后形成临时4节点的过程
private void flipColors(Node node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
情况2:右旋转过程(新添加的元素在最左侧):
- 颜色翻转相当于在2-3树中将一个新节点融合到一个3节点的左侧后形成一个临时的4节点的过程
- 进行右旋转,将x变为根节点,原根节点变为x的右子节点
- 右旋转后进行颜色改变,将新的根节点x设为原根节点node的颜色,将node设为红色
- 接下来,根节点需要向上融合,即进行一次颜色翻转操作
//辅助函数,将节点node进行右旋转,旋转后修改颜色,返回旋转后的根节点
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;
}
情况3:颜色翻转,右旋转分别用于新添加的元素在4节点的最右侧和最左侧的情况,那么,当新添加的元素在4节点的中间时呢?
- 此时,需要三步操作:将中间的节点进行左旋转;将根节点进行右旋转并维护颜色;颜色翻转
2.2 添加节点的过程
n
- 在一个2节点中融入一个新节点,如果新节点是左倾的,不需要任何操作,如果新节点是右倾的,进行一次左旋转
- 在一个3节点中融入一个新节点时,根据新节点加入的位置,选择下面操作的起始位置
- 综合以上两点,需要左旋转的条件为:isRed(node.right) && !isRed(node.left); 需要右旋转的条件为:isRed(node.left) && isRed(node.left.left); 需要颜色翻转的条件为:isRed(node.left) && isRed(node.right)
//维护红黑树黑节点的绝对平衡: 根据添加节点的不同情况,进行左旋转,右旋转,颜色翻转操作,三个操作次序不能颠倒
//左旋转时机
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);
}
三. 红黑树性能总结
- 对于完全随机的数据,普通的二分搜索树就很好用,因为普通的二分搜索树没有维护平衡的一些额外的繁琐操作。但二分搜索树在极端情况下会退化成链表(或者高度不平衡)
- 对于查询,修改(修改首先需要查询)较多的情况,AVL树很好用
- 相对于AVL树,红黑树牺牲了平衡性(2logn高度),但添加,删除性能优于AVL树,且统计性能(综合增删改查所有操作)更优
四. 红黑树xian相关问题
- 左倾红黑树和右倾红黑树
- 另一种统计性能优秀的树结构:伸展树(Splay Tree),其运用了局部性原理,即刚被访问的内容下次高概率被再次访问
- 基于红黑树的TreeSet和TreeMap(java标准库的实现)
- 红黑树有其它的具体优化和实现方法,对于更优的红黑树,任何不平衡都会在三次旋转内解决
在这里所介绍的红黑树(包括《算法4》所介绍的红黑树),是一种特殊的红黑树——左倾红黑树。但是红黑树本身并不需要“左倾”性质。也就是说,左倾红黑树一定是红黑树;但是红黑树不一定全是左倾红黑树。这里,为了维护“左倾”这个性质,做了额外的事情,消耗了性能,所以没有“任何不平衡都可以在三次旋转内解决”这么好的性能优势。
红黑树具有五个性质,只要满足着五个性质,就是红黑树!这五个性质是红黑树的标准定义,即:
- 所有节点非红即黑;
- 根节点为黑;
- 最后的NULL节点为黑;
- 红节点的孩子一定为黑;
- 黑平衡
仔细观察这五个性质,发现:红黑树的标准定义中没有规定红节点一定左倾。换句话说,以下的两种形状,是满足红黑树的性质的!(其中B代表黑节点;R代表红节点):
B
/ \
B RB
/ \
R R
让红黑树也可以包含上面两种样子,就是红黑树的更优实现。当我们放宽限制,让红黑树也可以包含上面两种样子的时候,我们需要的旋转调整将减少,并且有对于任何不平衡,三次旋转即可解决!
思考一下,如果以上的两个样子也符合红黑树的定义,意味着什么呢?
仔细观察一下其中的第二种样子:
B
/ \
R R
根据红黑树和2-3树的关系,这其实意味着红黑树不仅仅是和2-3树等价的,还和2-4树等价(包含2-node,3-node,4-node)。但是,对于其中的4-node,我们只能用以上的样子表示,而不能使用以下两种形状:B B
/ \
R R
/ \
R R
这是因为以上两种形状,他们破坏了红黑树的性质4!在这里,请大家再回顾一下红黑树的五个性质!
有时候,其中前三个性质,都是非常记忆的很朴素的定义。对于性质3,我们又称又称为“红色性质”,因为它是和红色节点相关的;相应的,性质5,我们有时候又称为“黑色性质”,因为它是和黑色节点相关的。同时,通过这个角度,用2-4树去理解《算法导论》中红黑树的实现,或许更容易。