红黑树是一个二叉搜索树,具有如下规则:
- 每个节点不是红色就是黑色。
- 根节点必须为黑色。
- 如果节点为红,其子节点必须为黑,父子节点不得同时为红。
- 任一节点至NULL(NULL为黑色)的任何路径,所含黑节点数必须相同。
根据规则4,新增节点必须为红。
根据规则3,新增节点的父节点必须为黑。
因为新增节点必须是红,那么只有在父节点不为黑的时候才需要调整,父节点为黑则无需调整。
着色法则的一个推论(证明过程未了解)是,红黑树的高度最多是2log(N+1)。因此,查找保证是一种对数的操作。
红黑树的平衡性比AVL树要弱,但红黑树通常能够导致良好的平衡状态。经验告诉我们,红黑树的搜索平均效率和AVL树几乎相等(STL源码剖析P210)。
红黑树的调整大体分三种情况,单旋转和双旋转非常类似于AVL树的旋转方法:
1、叔父节点为黑色,新节点在外侧插入。调整方法是:父节点和祖父节点单旋转并改变颜色,如下图。
2、叔父节点为黑色,新节点在内侧插入。调整方法是:先用新节点和父节点执行一次单旋转,如下图。
右图和第一种情况一样了,执行一次单旋转再变色即可。
3、叔父节点为红色,即祖父节点为黑,父节点和叔父节点为红。调整方法是:改变祖父、父、叔父颜色。
如果节点G的父节点为红,那么使用前两种方法进行调整即可。
从上面的插入操作可以看出,不管是内侧插入还是外侧插入,只要叔父节点颜色为黑,那么执行相应的单旋转或双旋转即可。但如果叔父节点为红,则需要改变三个节点的颜色。上图中,G的父节点又有可能为红,那么就要根据G的叔父节点的颜色持续向上改变,这是自底向上的插入方法,STL中的红黑树就用了这种做法(源码剖析P225)。另外一种做法是自顶向下,在沿着路径搜寻插入点的同时,只要发现某个黑节点的两个儿子全为红,则改变颜色,从而避免了持续向上的改变,《数据结构与算法分析》中的例程就用了这种方法(P356)。
下面是代码:
#include <stdio.h> typedef int ElementType; typedef struct RedBlackNode *Position; typedef Position RedBlackTree; typedef enum ColorType {Red, Black} ColorType; struct RedBlackNode { ElementType Element; RedBlackTree Left; RedBlackTree Right; ColorType Color; }; Position NullNode = NULL; // 初始化两个特殊节点:根标记和NULL节点 RedBlackTree Initialize(void) { RedBlackTree T; if (NullNode == NULL) { NullNode = malloc(sizeof (struct RedBlackNode)); if (NullNode == NULL) return -1; NullNode->Left = NullNode->Right = NullNode; NullNode->Element = 65535; NullNode->Color = Black; // NULL节点为黑色 } T = malloc(sizeof (struct RedBlackNode)); if (T == NULL) return -1; T->Left = T->Right = NullNode; T->Element = -65535; T->Color = Black; return T; } Position SingleRotate_Left(Position T) { Position NewRoot; NewRoot = T->Left; T->Left = NewRoot->Right; NewRoot->Right = T; return NewRoot; } Position SingleRotate_Right(Position T) { Position NewRoot; NewRoot = T->Right; T->Right = NewRoot->Left; NewRoot->Left = T; return NewRoot; } static Position Rotate(Position Parent, ElementType Item) { if (Item < Parent->Element) { if (Item < Parent->Left->Element) return Parent->Left = SingleRotate_Left(Parent->Left); // 左-左 else return Parent->Left = SingleRotate_Right(Parent->Left); // 左-右 } else { if (Item > Parent->Right->Element) return Parent->Right = SingleRotate_Right(Parent->Right); // 右-右 else return Parent->Right = SingleRotate_Left(Parent->Right); // 右-左 } } static Position x, p, gp, ggp; static void HandleReorient(RedBlackTree T, ElementType Item) { x->Color = Red; x->Left->Color = Black; x->Right->Color = Black; if (p->Color == Red) { gp->Color = Red; if ((Item < gp->Element) != (Item < p->Element)) p = Rotate(gp, Item); // 之字形,双旋转 x = Rotate(ggp, Item); x->Color = Black; } T->Right->Color = Black; } // 每进行一次插入都要从上至下的遍历 RedBlackTree Insert(RedBlackTree T, ElementType Item) { gp = p = x = T; NullNode->Element = Item; while (x->Element != Item) { ggp = gp; gp = p; p = x; if (Item < x->Element) x = x->Left; else x = x->Right; if (x->Left->Color == Red && x->Right->Color == Red) HandleReorient(T, Item); } if (x != NullNode) return T; x = malloc(sizeof (struct RedBlackNode)); if (x == NULL) return -1; x->Element = Item; x->Left = x->Right = NullNode; if (Item < p->Element) p->Left = x; else p->Right = x; HandleReorient(T, Item); return T; } static void MidPrint(RedBlackTree T) { if (T != NullNode) { MidPrint(T->Left); printf("%d ", T->Right->Element); MidPrint(T->Right); } } int main() { RedBlackTree tree; tree = Initialize(); tree = Insert(tree, 10); tree = Insert(tree, 85); tree = Insert(tree, 15); tree = Insert(tree, 70); tree = Insert(tree, 20); tree = Insert(tree, 60); tree = Insert(tree, 30); tree = Insert(tree, 50); tree = Insert(tree, 65); tree = Insert(tree, 80); tree = Insert(tree, 90); tree = Insert(tree, 40); tree = Insert(tree, 5); tree = Insert(tree, 55); MidPrint(tree); return 0; }
参考:
《STL源码剖析》 P208.
《数据结构与算法分析》 P351.
何时开始改变?就是现在了