一、概念
红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长2倍,因而是近似于平衡的。
二、定义
一棵红黑树是满足下面红黑性质的二叉搜索树:
1、每个结点或是红色,或是黑色;
2、根节点是黑色的;
3、每个叶节点(NIL)是黑色的;
4、如果一个结点是红色的,则它的两个子结点都是黑色的;
5、对每个结点,从该结点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点(此黑色结点的数目称为黑高)。
三、和平衡二叉树的区别
平衡二叉树是完全平衡的,而红黑树是局部平衡的,能确保没有一条路径会比其他路径长2倍,并且调整少,性能高,所以得到广泛应用,在STL里map和set都由红黑树实现。
四、数据结构
#pragma once #define RED 0 #define BLACK 1 struct Node{ Node(int key){ this->key=key; } int key; int color; Node* parent; Node* left; Node* right; }; struct Tree{ Tree(){ } Node* root; Node* nil; };
五、示例(图中所有空指针指向叶结点NIL)
六、旋转
目的:对红黑树进行插入和删除,可能会导致该树不在满足红黑树的性质,为了维护红黑树的性质,必须要改变树中某些结点的颜色以及指针结构。改变指针结构即通过旋转来完成,这是一种能保持二叉搜索树性质的搜索树局部操作。
6.1 左旋(LEFT_ROTATE)
图示:由三部完成该操作。
代码:
//节点左旋 void LEFT_ROTATE(Tree* &T, Node* x){ Node* y; y=x->right; x->right=y->left; if(y->left!=T->nil) y->left->parent=x; y->parent=x->parent; if(x->parent==T->nil) T->root=y; else if(x==x->parent->left) x->parent->left=y; else x->parent->right=y; y->left=x; x->parent=y; }
6.2 右旋(LEFT_ROTATE)
图示:由三部完成该操作。
代码:
七、插入
//节点右旋 void RIGHT_ROTATE(Tree* &T, Node* y){ Node *x; x=y->left; y->left=x->right; if(x->right!=T->nil) x->right->parent=y; x->parent=y->parent; if(y->parent==T->nil) T->root=x; else if(y==y->parent->left) y->parent->left=x; else y->parent->right=x; x->right=y; y->parent=x; }
插入操作与上一篇博文二叉排序树插入操作基本相同,除了细节之处稍有改变。
代码:
//插入节点 void RB_INSERT(Tree* &T,Node* z){ Node* y=T->nil; Node* x=T->root; while(x!=T->nil){ y=x; if(z->key < x->key) x=x->left; else x=x->right; } z->parent=y; if(y==T->nil)//插入第一个元素 T->root=z; else if(z->key < y->key) y->left=z; else y->right=z; z->left=T->nil; z->right=T->nil; z->color=RED; RB_INSERT_FIXUP(T,z); }
八、染色和调整
由于插入操作可能破坏红黑树性质,通过RB_INSERT_FIXUP(T,z)函数调整结点颜色和树的结构,使其保持红黑性质。
//红黑树调整 void RB_INSERT_FIXUP(Tree* &T, Node* z){ Node* y; while(z->parent->color==RED){ if(z->parent==z->parent->parent->left){ //z节点父节点为其祖父节点的左孩子 y=z->parent->parent->right; if(y->color==RED){ //case1 z->parent->color=BLACK; y->color=BLACK; z->parent->parent->color=RED; z=z->parent->parent; } else{ if(z==z->parent->right){ //case2 z=z->parent; LEFT_ROTATE(T,z); } z->parent->color=BLACK; //case3 z->parent->parent->color=RED; RIGHT_ROTATE(T,z->parent->parent); } } else{ //z节点父节点为其祖父节点的右孩子 y=z->parent->parent->left; if(y->color==RED){ //case 1 z->parent->color=BLACK; y->color=BLACK; z->parent->parent->color=RED; z=z->parent->parent; } else{ if(z==z->parent->left){ //case2 z=z->parent; RIGHT_ROTATE(T,z); } z->parent->color=BLACK; //case3 z->parent->parent->color=RED; LEFT_ROTATE(T,z->parent->parent); } } } T->root->color=BLACK; }
九、测试函数
十、实验总结
int main(){ Tree* T; Node* z[MAXSIZE]; int arr[MAXSIZE]={12,1,9,2,0,11,7,19,4,15, 18, 5, 14, 13, 10, 16, 6, 3, 8, 17}; T=new Tree(); T->nil=new Node(0); T->nil->color=BLACK; T->root=T->nil; for(int i=0;i<MAXSIZE;i++){ z[i]=new Node(arr[i]); RB_INSERT(T,z[i]); } PreOrder(T->root,T); cout << endl; InOrder(T->root,T); return 0; }
本次实验红黑树插入算法实现中遇到了很多bug,最难以察觉的bug是在节点旋转的过程中,很可能把根节点旋丢了,经过多次分析和变量追踪,最后采取的方法是:为红黑树单独创建一个结构体,专门用来跟踪根节点和nil节点,最终问题得到完美解决,另外试验中还出现的错误包括边界条件处理,尤其是给空节点指定父指针这样的错误,还有if条件语句中误把“==”比较运算符,写为“=”赋值运算符,最终通过数据测试和断点追踪顺利找出错误。另外经过实验发现红黑树是一种效率比较高的树,可在O(logn)的时间内完成节点查找和插入。