平衡二叉树之红黑树(Red-Black Tree)简介及Java实现
与其他平衡二叉树不同,红黑树的每个节点有个额外的位来存储节点的颜色(红色或者黑色)。这些颜色位保证了在树的插入和删除时能保持平衡。
尽管红黑树的平衡不是完美的,但是它也足够保证搜索的时间效率为$O(\log n)$。追踪节点的颜色仅仅需要每个节点多保存一位数据,因为它只有两种颜色。除此之外,它不保存任何其他信息,因此,它的内存使用和典型的差不多,并不会造成额外的内存占用。
加载中...
与其他平衡二叉树不同,红黑树的每个节点有个额外的位来存储节点的颜色(红色或者黑色)。这些颜色位保证了在树的插入和删除时能保持平衡。
尽管红黑树的平衡不是完美的,但是它也足够保证搜索的时间效率为$O(\log n)$。追踪节点的颜色仅仅需要每个节点多保存一位数据,因为它只有两种颜色。除此之外,它不保存任何其他信息,因此,它的内存使用和典型的差不多,并不会造成额外的内存占用。
欢迎关注 DataLearner 官方微信,获得最新 AI 技术推送

红黑树依靠节点的颜色来调整树的平衡(这和AVL树使用平衡因子来判断类似,后面会具体描述),其对颜色的要求如下:
这里说一下红黑树的叶子节点是不包含任何信息的,这些叶子不需要在计算机内存中显式存在,使用空子指针就可以编码这个叶子节点。但是如果叶子确实是显式节点,它会简化了一些在红黑树上操作的算法。所以,为了节省执行时间,有时指向单个sentinel节点(而不是空指针)的指针执行所有叶节点的角色; 从内部节点到叶节点的所有引用然后指向sentinel节点。因此,通常也称红黑树的叶子节点为NIL节点(NIL Leaves,NIL是0的意思,暗指不包含任何信息)。
如下图就是一个典型的红黑树:

红黑树的插入和删除操作会破坏树的颜色结构,所以需要重新调整节点的颜色来保证红黑树的性质符合上述要求,但这种操作消耗的计算也很小,因此依然可以保证时间复杂度在$O(\log n)$。
红黑树的调整包括旋转和变色两种。根据具体不同的情况来操作。以插入数据为例,典型的二叉查找树的插入操作是顺着二叉树向下,把新元素变成新的叶子节点。但是在红黑树中的操作不是这样,新元素的键会根据典型的二叉搜索树往下寻找,到大叶子节点(NIL 节点)后,会将该叶子节点去掉,然后把这个新元素变成这里的节点,并在新元素后面添加两个叶子节点。同时,该新节点的颜色是红色,新加的叶子节点的颜色是黑色。
完成上述操作之后,需要根据新加的节点周围的节点情况(这里共有4中case,后面表格中的case编号对应这里的情况)来进行调整(假设新加的节点是N):
在前面的规则中:
上述四种情况对应的操作如下:
红黑树与AVL树相比没有那么严格的平衡,但是查找、插入和删除的效率是一样的。因此在实际中红黑树更广泛被使用。因为红黑树的实现相对简单,在常规操作上也更容易。JDK里面的TreeMap、TreeSet和HashMap(JDK 8)都使用红黑树来实现。
值得注意的是,在插入操作的时候,AVL树需要执行的旋转次数更多,有$O(\log n)$,而后者只需要$O(1)$。更多的旋转意味着更多的内存写入,这也是耗时的操作。所以更新操作的时候红黑树比AVL树更快。
具体的,StackOverflow上也有人给了一个比较:
对于小数据集:
对于大规模数据集:
package com.huawei.machinelearning.data.structure;// RedBlackTree class
//
// CONSTRUCTION: with a negative infinity sentinel
//
// ******************PUBLIC OPERATIONS*********************
// void insert( x ) --> Insert x
// void remove( x ) --> Remove x (unimplemented)
// Comparable find( x ) --> Return item that matches x
// Comparable findMin( ) --> Return smallest item
// Comparable findMax( ) --> Return largest item
// boolean isEmpty( ) --> Return true if empty; else false
// void makeEmpty( ) --> Remove all items
// void printTree( ) --> Print all items
// ******************ERRORS********************************
// Exceptions are thrown by insert if warranted and remove.
/**
* Implements a red-black tree.
* Note that all "matching" is based on the compareTo method.
* @author Mark Allen Weiss
*/
public class RedBlackTree {
/**
* Construct the tree.
*/
public RedBlackTree( ) {
header = new RedBlackNode( null );
header.left = header.right = nullNode;
}
/**
* Compare item and t.element, using compareTo, with
* caveat that if t is header, then item is always larger.
* This routine is called if is possible that t is header.
* If it is not possible for t to be header, use compareTo directly.
*/
private final int compare( Comparable item, RedBlackNode t ) {
if( t == header )
return 1;
item.compareTo( t.element );
}
{
current = parent = grand = header;
nullNode.element = item;
( compare( item, current ) != ) {
great = grand; grand = parent; parent = current;
current = compare( item, current ) < ?
current.left : current.right;
( current.left.color == RED && current.right.color == RED )
handleReorient( item );
}
( current != nullNode )
( item.toString( ) );
current = ( item, nullNode, nullNode );
( compare( item, parent ) < )
parent.left = current;
parent.right = current;
handleReorient( item );
}
{
( );
}
Comparable {
( isEmpty( ) )
;
header.right;
( itr.left != nullNode )
itr = itr.left;
itr.element;
}
Comparable {
( isEmpty( ) )
;
header.right;
( itr.right != nullNode )
itr = itr.right;
itr.element;
}
Comparable {
nullNode.element = x;
current = header.right;
( ; ; ) {
( x.compareTo( current.element ) < )
current = current.left;
( x.compareTo( current.element ) > )
current = current.right;
( current != nullNode )
current.element;
;
}
}
{
header.right = nullNode;
}
{
printTree( header.right );
}
{
( t != nullNode ) {
printTree( t.left );
System.out.println( t.element );
printTree( t.right );
}
}
{
header.right == nullNode;
}
{
current.color = RED;
current.left.color = BLACK;
current.right.color = BLACK;
( parent.color == RED )
{
grand.color = RED;
( ( compare( item, grand ) < ) !=
( compare( item, parent ) < ) )
parent = rotate( item, grand );
current = rotate( item, great );
current.color = BLACK;
}
header.right.color = BLACK;
}
RedBlackNode {
( compare( item, parent ) < )
parent.left = compare( item, parent.left ) < ?
rotateWithLeftChild( parent.left ) :
rotateWithRightChild( parent.left ) ;
parent.right = compare( item, parent.right ) < ?
rotateWithLeftChild( parent.right ) :
rotateWithRightChild( parent.right );
}
RedBlackNode {
k2.left;
k2.left = k1.right;
k1.right = k2;
k1;
}
RedBlackNode {
k1.right;
k1.right = k2.left;
k2.left = k1;
k2;
}
{
RedBlackNode( Comparable theElement ) {
( theElement, , );
}
RedBlackNode( Comparable theElement, RedBlackNode lt, RedBlackNode rt ) {
element = theElement;
left = lt;
right = rt;
color = RedBlackTree.BLACK;
}
Comparable element;
RedBlackNode left;
RedBlackNode right;
color;
}
RedBlackNode header;
RedBlackNode nullNode;
{
nullNode = ( );
nullNode.left = nullNode.right = nullNode;
}
;
;
RedBlackNode current;
RedBlackNode parent;
RedBlackNode grand;
RedBlackNode great;
{
( );
;
;
System.out.println( );
( GAP; i != ; i = ( i + GAP ) % NUMS )
t.insert( ( i ) );
( ((Integer)(t.findMin( ))).intValue( ) != ||
((Integer)(t.findMax( ))).intValue( ) != NUMS - )
System.out.println( );
( ; i < NUMS; i++ )
( ((Integer)(t.find( ( i ) ))).intValue( ) != i )
System.out.println( );
}
}
{
{
( );
}
{
( message );
}
}
参考文献:
https://stackoverflow.com/questions/16257761/difference-between-red-black-trees-and-avl-trees https://www.java-tips.org/java-se-tips-100019/24-java-lang/1904-red-black-tree-implementation-in-java.html https://en.wikipedia.org/wiki/Red%E2%80%93black_tree