本文由@呆代待殆原创,转载请注明出处:http://www.cnblogs.com/coffeeSS/
学习红黑树的前置技能:二叉搜索树http://www.cnblogs.com/coffeeSS/p/5452719.html
红黑树简介
红黑树是一种自平衡(平衡指所有叶子的深度趋于相等)二叉查找树,它是在1972年由1972年由鲁道夫·贝尔发明,这个数据结构对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保(也就是说同样都在最坏情况下,他的速度比一般的树的速度要快)无论是查找,插入还是删除都可以在O(lgn)内完成,所以很适合用在时间敏感的场合中。
红黑树的性质
红黑树就是有颜色的二叉查找树,并且要满足以下5个约束:
1,节点不是红色就是黑色。
2,根节点是黑色。
3,所有叶子节点都是黑色(这里所有叶子节点指的是NIL节点)。
4,每个红色节点的两个孩子都是黑色。
5,任意一个节点到其每个叶子的所有简单路径(简单路径指路径中不会出现重复节点)都包含相同数目的黑色节点。
红黑树的每个节点至少包含5个属性:color、key、left、right与parent。
如果一个节点没有子节点或者父节点则相应的指针设为null或者指向NIL节点。
根节点是唯一一个parent为NIL的节点。
红黑树的所有叶子节点都是NIL节点,但是在很多实例图中这些NIL节点都会被省略,而且实际实现中,如果真的实现NIL节点,一般只会设置一个NIL节点,让所有指向叶子节点的节点都指向这个节点,根节点的parent指针也会指向这个节点(当然你简单的将这些指向NIL节点的指针都设成null也是可以的,但是本文的代码实现是基于NIL节点的,因为这样代码写起来更自然一点)。
NIL节点的作用是充当哨兵和完善红黑树的结构,它的color属性为black,其他属性都没有实际意义,设为null或者不管都可以。
下面看一张来自WIKI百科的红黑树的图例
1 enum Color{black,red}; 2 3 struct Node{ 4 Node(){ color = black; } 5 Node(int k) :key(k){ color = red; } 6 Node* parent = nullptr; 7 Node* left = nullptr; 8 Node* right = nullptr; 9 Color color; 10 int key; 11 }; 12 13 class MyRBT{ 14 private: 15 Node* nil=new Node(); 16 Node* root=nil; 17 void fix_color(Node* n);//插入过程中有可能递归的部分 18 Node* getUncle(Node* n);//返回n的叔节点 19 void left_rotate(Node* n);//左旋 20 void right_rotate(Node* n);//右旋 21 Node*& parentToN(Node* n);//获得n的父节点里指向n的那个指针 22 Node* getSibling(Node* n);//返回n的兄弟节点 23 bool isParentLeftChild(Node* n);//判断n是不是父节点的左孩子 24 void delete_fix_color(Node* n);//删除过程中有可能递归的部分。 25 public: 26 MyRBT(){}; 27 MyRBT(vector<int> v); 28 Node* getRoot(){ return root; } 29 void insertNode(int k); 30 void deleteNode(int k); 31 Node* findByKey(int k); 32 Node* findSuccessor(int k); 33 Node* findPredecessor(int k); 34 void traversal(Node* root, void(*f)(int k) = [](int k)->void{cout << k << " "; });//遍历输出所有节点 35 void insertNode(Node* n); 36 void deleteNode(Node* m); 37 Node* findSuccessor(Node* n);//找n的后继 38 Node* findPredecessor(Node* n);//找n的前驱 39 };
其中除了插入和删除操作,其他操作和二叉搜索树没什么区别,只是这里的版本都是基于NIL节点实现的,而之前的二叉搜索树那篇是基于nullptr实现的,本质上没有任何区别,所以本文重点介绍插入删除操作。
![](https://img-blog.csdnimg.cn/2022010623113329579.png)
1 Node*& MyRBT::parentToN(Node* n){ 2 if (n->parent == nil) return nil; 3 if (n->parent->left == n) 4 return n->parent->left; 5 else return n->parent->right; 6 }
然后我们给出左旋和右旋操作的代码,结合上面的图来理解比较好。
1 void MyRBT::left_rotate(Node* n){ 2 if (n->right == nil)//设n的右孩子为y 3 cout << "left_rotate error" << endl; 4 parentToN(n) = n->right;//修改n的父节点指向y 5 n->right->parent = n->parent;//修改y的parent指针 改y->parent 为 n->parent 6 n->parent = n->right;//修改n的parent指针 改n->parent 为 n->right 7 n->right = n->right->left;//将y的左子树变成n的右子树 改n->right 为 y-left 8 n->parent->left = n;//将n改成y的左孩子 ,这个时候取原来n的右孩子不能通过n->right,因为上一句中已经改变了r->right,但是看到上上一句就知道n->parent现在指向n的原先的右孩子 改y->left 为 n 9 } 10 void MyRBT::right_rotate(Node* n){ 11 if (n->left == nil) 12 cout << "right_rotate error" << endl; 13 parentToN(n) = n->left; 14 n->left->parent = n->parent; 15 n->parent = n->left; 16 n->left = n->left->right; 17 n->parent->right = n; 18 }
1 Node* MyRBT::getUncle(Node* n){ 2 if (n->parent != nil&&n->parent->parent != nil){ 3 if (n->parent->parent->left == n->parent) 4 return n->parent->parent->right; 5 else return n->parent->parent->left; 6 } 7 return nullptr; 8 }
1 void MyRBT::insertNode(int k){ 2 insertNode(new Node(k)); 3 } 4 5 void MyRBT::insertNode(Node* n){ 6 Node* temp = root; 7 if (temp == nil){ 8 root = n; 9 root->parent = nil;//root的父节点指向nil 10 root->left = nil; 11 root->right = nil; 12 fix_color(n); 13 return; 14 } 15 while (true){ 16 if (temp->key > n->key){ 17 if (temp->left != nil) 18 temp = temp->left; 19 else{ 20 temp->left = n; 21 n->parent = temp; 22 n->right = nil; 23 n->left = nil; 24 fix_color(n); 25 return; 26 } 27 } 28 else{ 29 if (temp->right != nil) 30 temp = temp->right; 31 else{ 32 temp->right = n; 33 n->parent = temp; 34 n->right = nil; 35 n->left = nil; 36 fix_color(n); 37 return; 38 } 39 } 40 } 41 } 42 43 //nil节点的存在是为了保证数结构的完整性和规范性 44 45 void MyRBT::fix_color(Node* n){ 46 if (n->parent == nil)//情况一 47 n->color = black; 48 else if (n->parent->color == black){//情况二 49 //无需任何处理 50 } 51 else if (getUncle(n)->color == red){//情况三 52 n->parent->color = black; 53 getUncle(n)->color = black; 54 n->parent->parent->color = red; 55 fix_color(n->parent->parent); 56 } 57 else { 58 if (n == n->parent->parent->left->right){// 头两个if是同时进行了情况四和情况五,这样写是因为旋转操作次数不 59 left_rotate(n->parent); //一样,各个节点的位置关系不一样,不能把这两个过程完全分开,看看后面的 60 right_rotate(n->parent); //颜色处理就会明白。 61 n->color = black; 62 n->left->color = red; 63 n->right->color = red; 64 } 65 else if (n == n->parent->parent->right->left){ 66 right_rotate(n->parent); 67 left_rotate(n->parent); 68 n->color = black; 69 n->left->color = red; 70 n->right->color = red; 71 } 72 else if (n == n->parent->parent->left->left){//这两个if是没有经过情况四直接进行情况五的时候。 73 right_rotate(n->parent->parent); 74 n->parent->color = black; 75 n->parent->left->color = red; 76 n->parent->right->color = red; 77 } 78 else { 79 left_rotate(n->parent->parent); 80 n->parent->color = black; 81 n->parent->left->color = red; 82 n->parent->right->color = red; 83 } 84 } 85 }
状态一:
描述:N是根节点。
操作:什么都不用做了,因为树空了。
影响:无。
后续:退出。
状态二:
描述:S节点是红色。
操作:将P和S的颜色交换,然后对P节点进行左旋(到此树的结构还是没有正常之后还要经过4,5 or 6,同样接下来的操作还是通过N节点来定位)。
影响:
结构:因为进行了左旋所以有很多变化详细请看图。
颜色:各个路径上的黑色节点数没有变化,但是S(左旋后的位置)的左子树的红色节点多了一个,而右子树的红色节点少了一个。
后续:判定现在的状态决定进入状态三、四、五或者六中的一个。
状态三:
描述:P、S、S的孩子都是黑色。
操作:我们直接把S染成红色。
影响:
结构:无变化。
颜色:P的右子树路径上的黑色节点数少了一个,而红色节点多了一个。
后续:这样一来经过P节点的路径就会比不经过P节点的路径少一个黑色节点(P节点以下的颜色矛盾已经解决),P节点现在的处境和之前的N节点一模一样,所以我们对P进行颜色的修正,进行递归调用。
状态四:
描述:S和S的孩子是黑色的,但是P是红的。
操作:那么我们就将S和P的颜色交。
影响:
结构:无变化
颜色:P的左子树路径黑色节点数量加一,P的右子树路径黑色节点数量不变
后续:状态修复退出。
状态五:
描述:S是黑色,S的左孩子是红的,S的右孩子是黑的。
操作:我们对S进行右旋,然后交换S和他的新父节点的颜色。
影响:
结构:由于进行了右旋所以结构变化很大,请直接看图。
颜色:就黑色节点而言没有任何一条路径有改变,但是,红色节点的位置发生了变化,N节点现在有一个有红色节点当右孩子的黑色兄弟。
后续:进入状态六。
状态六:
描述:S是黑色的,S的右节点是红色的。
操作:对P进行左旋,然后交换P和S的颜色,并将S的右孩子变成黑色
影响:
结构:由于进行了左旋所以结构变化很大,请直接看图。
颜色:S(左旋后)的右子树路径的黑色节点的数目不变,左子树路径的黑色节点增加了一个。
后续:状态修复退出。
这样来看的话我们一共有8条可能的路径来修复(假设1-6代表进行状态1-6对应的操作)
路径:
1(问题解决)
3(解决局部问题,向上传递到状态1)
2,4(问题解决)
2,5,6(问题解决)
2,6(问题解决)
4(问题解决)
5,6(问题解决)
6(问题解决)
然后让我们来假设N是父节点的右孩子,那么那些操作和情况会发生变化呢?(下面说的不会变化指的是上面的文字描述不会有变化)
状态一,不会有变化。
状态二,对P节点的左旋会变成右旋,其他操作不变。
状态三,不会有变化。
状态四,不会有变化。
状态五,S的左孩子是黑色,S的右孩子是红色,对S进行左旋,其他不变。
状态六,S的左节点是红色,对P进行右旋,将S的左孩子染成黑色,其他不变。
所以我们在写判定条件和操作的时候要记得把N是左右孩子的情况都考虑进去,我们综合一下应该是下面这样。在M与C都是黑色的情况下
状态一:
描述:N是根节点。
操作:什么都不用做了,因为树空了。
状态二:
描述:S节点是红色。
操作:将P和S的颜色交换,然后如果N是P的左孩子则对P节点进行左旋。
如果N是P的右孩子则对P节点进行右旋。
状态三:
描述:P、S、S的孩子都是黑色。
操作:我们直接把S染成红色。
状态四:
描述:S和S的孩子是黑色的,但是P是红的。
操作:那么我们就将S和P的颜色交。
状态五:
描述:S是黑色,S的左孩子是红的,S的右孩子是黑的,N是P的左孩子。
或者
S是黑色,S的左孩子是黑的,S的右孩子是红的,N是P的右孩子。
操作:N是P的左孩子我们对S进行右旋。
或者
N是P的右孩子我们对S进行左旋。
然后交换S和他的新父节点的颜色。
状态六:
描述:S是黑色的,S的右节点是红色的,N是P的左孩子。
或者
S是黑色的,S的左节点是红色的,N是P的右孩子。
操作:N是P的左孩子我们对P进行左旋,然后交换P和S的颜色,并将S的右孩子变成黑色。
或者
N是P的右孩子我们对P进行右旋,然后交换P和S的颜色,并将S的左孩子变成黑色。
现在让我们来集中解决一些常见问题。
问题一:情况三中S节点不会是NIL节点么?
答:不会,可以用假设法,假设S是NIL节点,那么为了保证每条路径上的黑色节点数相同,那么M节点必须是NIL节点,如果M是非NIL节点,则一定会导致这个非NIL节点的路径上的黑色节点数多于经过S节点的路径,而M在情况三中并不是NIL节点,M的两个孩子才是。
问题二:N节点是NIL节点,删除算法中把它当做普通节点对待没有问题么?
答:NIL节点和null值是不同的,NIL节点是一个空的节点,它也具有节点的结构,但是在这里NIL里有意义的数据只有color,其他的数据都是没有意义的,所以只要算法结束后红黑树的结构维持不涉及NIL节点中color以外的属性就没有问题。(如果你想用null来代替NIL节点实现算法也是可以的,但是很多细节需要有相应的修改,博主觉得还是用NIL节点比较方便)。
现在让我们来看代码实现,为了使代码更简洁我们再写两个辅助函数
Node* getSibling(Node* n);//返回n的兄弟节点
bool isParentLeftChild(Node* n);//判断n是不是父节点的左孩子
1 bool MyRBT::isParentLeftChild(Node* n){ 2 if (n->parent == nil){ 3 cout << "isParentLeftChild error" << endl; 4 exit(0); 5 } 6 if (n->parent->left == n) return true; 7 else return false; 8 } 9 10 Node* MyRBT::getSibling(Node* n){ 11 if (n->parent == nil||n==nullptr) return nil; 12 if (n->parent->left == n) 13 return n->parent->right; 14 else return n->parent->left; 15 }
因为删除算法涉及到查找前驱和后继,所以这个代码也贴在这里
1 Node* MyRBT::findSuccessor(int k){ 2 return findSuccessor(findByKey(k)); 3 } 4 Node* MyRBT::findSuccessor(Node* n){ 5 if (n->right != nil){ 6 n = n->right; 7 while (n->left != nil) 8 n = n->left; 9 return n; 10 } 11 while (n->parent != nil&&n->parent->right == n) 12 n = n->parent; 13 return n->parent; 14 } 15 16 Node* MyRBT::findPredecessor(int k){ 17 return findPredecessor(findByKey(k)); 18 } 19 Node* MyRBT::findPredecessor(Node* n){ 20 if (n->left != nil){ 21 n = n->left; 22 while (n->right != nil) 23 n = n->right; 24 return n; 25 } 26 while (n->parent != nil&&n->parent->left == n) 27 n = n->parent; 28 return n->parent; 29 }
1 void MyRBT::deleteNode(int k){ 2 deleteNode(findByKey(k)); 3 } 4 5 void MyRBT::deleteNode(Node* m){ 6 while(m->left != nil&&m->right != nil){//这个循环就是不断的找前驱(找后继也是可以的)来代替要被删除的节点,直达某个前驱最多只有一个非NIL节点的孩子 7 Node* temp = findPredecessor(m); 8 m->key = temp->key; 9 m = temp; 10 } 11 if (m->color == red) //情况一 12 parentToN(m) = nil; 13 else if (m->left->color == red || m->right->color == red){//情况二 14 if (m->left != nil){ 15 m->left->parent = m->parent; 16 parentToN(m) = m->left; 17 m->left->color = black; 18 } 19 else{ 20 m->right->parent = m->parent; 21 parentToN(m) = m->right; 22 m->right->color = black; 23 } 24 } 25 else delete_fix_color(m);//进入最复杂的情况三 26 delete m;//处理完释放被删除的节点的空间 27 return; 28 } 29 30 void MyRBT::delete_fix_color(Node* n){ 31 Node* s = getSibling(n); 32 Node* p = n->parent; 33 parentToN(n) = nil; 34 nil->parent = n->parent; 35 n = nil; 36 if (n->parent == nil){//状态一 37 //什么都不用做 38 } 39 else if (p->color == black&&s->color == black&&s->left->color == black&&s->right->color == black){//状态三 40 s->color = red; 41 delete_fix_color(p); 42 } 43 else { 44 if (s->color == red){//状态二 45 s->color = black; 46 p->color = red; 47 if (isParentLeftChild(n)) left_rotate(p); 48 else right_rotate(p); 49 } 50 if (s->color == black&&s->left->color == black&&s->right->color == black&&p->color == red){//状态四 51 s->color = red; 52 p->color = black; 53 return; 54 } 55 else{//状态五 56 if (s->color == black&&s->left->color == red&&s->right->color == black&&isParentLeftChild(n)){ 57 right_rotate(s); 58 s->color = red; 59 s->parent->color = black; 60 } 61 else if (s->color == black&&s->left->color == black&&s->right->color == red && (!isParentLeftChild(n))){ 62 left_rotate(s); 63 s->color = red; 64 s->parent->color = black; 65 } 66 //状态六 67 if (s->color == black&&s->right->color == red&&isParentLeftChild(n)){ 68 left_rotate(p); 69 s->color = p->color; 70 p->color = black; 71 s->right->color = black; 72 } 73 else if (s->color == black&&s->left->color == red && (!isParentLeftChild(n))){ 74 right_rotate(p); 75 s->color = p->color; 76 p->color = black; 77 s->left->color = black; 78 } 79 } 80 } 81 }
删除操作到此结束。
1 MyRBT::MyRBT(vector<int> v):MyRBT(){ 2 for (auto n : v){ 3 insertNode(n); 4 } 5 }
通过key值查找特定节点
1 Node* MyRBT::findByKey(int k){ 2 Node* temp = root; 3 while (temp != nil){ 4 if (k == temp->key) 5 return temp; 6 temp = k < temp->key ? temp->left : temp->right; 7 } 8 cout << "can't find" << endl; 9 return nullptr; 10 }
遍历算法
1 void MyRBT::traversal(Node* root, void(*f)(int k)){ 2 if (root==nil)//当树为空的时候root为nullptr 3 return; 4 traversal(root->left); 5 cout << root->key << " " << root->color << endl; 6 traversal(root->right); 7 }
参考资料:
1,《算法导论 中文版》(英文版第三版)(美)ThomasH.Cormen,CharlesE.Leiserson,RonaldL.Rivest,CliffordStein 著;王刚,邹恒明,殷建平,王宏志等译。
2,WIKI百科https://en.wikipedia.org/wiki/Red%E2%80%93black_tree