1. 插入实现
首先,我们先来看看插入相关的所有方法:
/**
* Balance Case One:
* root's left child is RED and
* the right child is BLACK
* */
private boolean ifBalanceCaseOne( RedBlackTreeNode root ) {
return isRed( root.right ) && !isRed( root.left );
}
/**
* Balance Case Two:
* root's left child is RED
* and the left child's left child is RED
* */
private boolean ifBalanceCaseTwo( RedBlackTreeNode root ) {
return isRed( root.left ) && isRed( root.left.left );
}
/**
* Balance Case Three:
* root's left child is BLACK and
* the right child is RED
* */
private boolean ifBalanceCaseThree( RedBlackTreeNode root ) {
return isRed( root.left ) && isRed( root.right );
}
/**
* balance this R-B tree
* */
private RedBlackTreeNode balance( RedBlackTreeNode root ) {
if ( ifBalanceCaseOne( root ) ) root = rotateLeft( root );
if ( ifBalanceCaseTwo( root ) ) root = rotateRight( root );
if ( ifBalanceCaseThree( root ) ) flipColors( root, false );
return ( RedBlackTreeNode ) updateSize( root );
}
/**
* put key -> val into this R-B tree
* */
public void put( K key, V val ) {
RedBlackTreeNode root = put( ( RedBlackTreeNode ) this.root, key, val );
root.color = BLACK;
this.root = root;
}
// this part of code is very similar to
// put( MapTreeNode<K, V> root, K key, V val ).
private RedBlackTreeNode put( RedBlackTreeNode root, K key, V val ) {
// base case, attach the new node to this position
if ( root == null ) return new RedBlackTreeNode( ID++, key, val, RED );
int res = compareKeys( root, key );
// the node should be attached in the left subtree
if ( res > 0 ) root.left = put( ( RedBlackTreeNode ) root.left, key, val );
// the node should be attached in the right subtree
else if ( res < 0 ) root.right = put( ( RedBlackTreeNode ) root.right, key, val );
// added before, update value
else root.val = val;
// update size and restore this R-B tree
return balance( root );
}
接下来,我们来解析相关代码,让大家更好的理解插入的实现。
首先我们看看put()的整体代码结构:
/**
* put key -> val into this R-B tree
* */
public void put( K key, V val ) {
RedBlackTreeNode root = put( ( RedBlackTreeNode ) this.root, key, val );
root.color = BLACK;
this.root = root;
}
// this part of code is very similar to
// put( MapTreeNode<K, V> root, K key, V val ).
private RedBlackTreeNode put( RedBlackTreeNode root, K key, V val ) {
// base case, attach the new node to this position
if ( root == null ) return new RedBlackTreeNode( ID++, key, val, RED );
int res = compareKeys( root, key );
// the node should be attached in the left subtree
if ( res > 0 ) root.left = put( ( RedBlackTreeNode ) root.left, key, val );
// the node should be attached in the right subtree
else if ( res < 0 ) root.right = put( ( RedBlackTreeNode ) root.right, key, val );
// added before, update value
else root.val = val;
// update size and restore this R-B tree
return balance( root );
}
我们先忽略balance(),大家应该能发现红黑树的put()和BST的put()其实是差不多的,都是沿着root,搜索需要插入的位置,然后插入新键。这也是为什么红黑树结构里面没有get(),因为红黑树的get()和BST是一模一样的,直接继承沿用即可。
那么,整个put()实现最关键的就是balance()里面的了。没错,这个方法的意义就在于,当我们再某个位置插入一个新键,我们需要对树进行自平衡。
/**
* Balance Case One:
* root's left child is RED and
* the right child is BLACK
* */
private boolean ifBalanceCaseOne( RedBlackTreeNode root ) {
return isRed( root.right ) && !isRed( root.left );
}
/**
* Balance Case Two:
* root's left child is RED
* and the left child's left child is RED
* */
private boolean ifBalanceCaseTwo( RedBlackTreeNode root ) {
return isRed( root.left ) && isRed( root.left.left );
}
/**
* Balance Case Three:
* root's left child is BLACK and
* the right child is RED
* */
private boolean ifBalanceCaseThree( RedBlackTreeNode root ) {
return isRed( root.left ) && isRed( root.right );
}
/**
* balance this R-B tree
* */
private RedBlackTreeNode balance( RedBlackTreeNode root ) {
if ( ifBalanceCaseOne( root ) ) root = rotateLeft( root );
if ( ifBalanceCaseTwo( root ) ) root = rotateRight( root );
if ( ifBalanceCaseThree( root ) ) flipColors( root, false );
return ( RedBlackTreeNode ) updateSize( root );
}
对于这个自平衡实现,也是非常的简单。大家还记得上一节的这张插入关系图么:
其实上面的自平衡代码就是对这张关系图的直接实现。我们知道case 2和case 3经过变换,可以变成case 1,case 1经过染色就完成了这个局部的自平衡操作,接下来我们只需要递归向上自平衡即可。
所以我们只需要依次检查是不是需要进行上面三种情况的自平衡,检查完以后,更新一下结点数即可。这就是balance()里面代码的意思:
if ( ifBalanceCaseOne( root ) ) root = rotateLeft( root );
if ( ifBalanceCaseTwo( root ) ) root = rotateRight( root );
if ( ifBalanceCaseThree( root ) ) flipColors( root, false );
return ( RedBlackTreeNode ) updateSize( root );
而且我们也不会担心出现没有自平衡的情况,因为上面的关系图已经说明了:如果遇到case 3 -> 染色后自平衡,如果遇到case 2 -> 右旋 -> case 3,如果遇到case 1 -> 左旋 -> case 2。所以不会出现经过检查,但该树局部没有自平衡的情况。
到此,红黑树的插入操作也讲解完毕。接下来,我们将进入本系列最难的 删除 - delete() 讲解。不过也不用太担心哒,慢慢来,如果第一遍不是很能理解,可以休息一下,再多看几遍哒。当时博主完全弄懂删除操作,用了4-5天,哈哈哈~
上一节:红黑树(三):插入·续
下一节:红黑树(五):删除最小键
系列汇总:超详细!红黑树详解文章汇总(含代码)
2. 特别感谢
- 感谢 @SENNICHEN 制作系列文章封面图
3. 免责声明
※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;