红黑树是一种平衡二叉查找树,不同于AVL树,红黑树必须满足一下几个规则: 1. 树中的节点不是为红色就是为黑色; 2. 根节点的颜色为黑色; 3. 若节点的颜色为红色,则其子节点必然为黑色; 4. 任意节点至NULL的路径上黑色节点的数目是相同的。 根据规则4,可知新增节点必须为红色;根据规则2和3,若节点为红色,则其父节点必然为黑色;根据规则3,若插入节点的父节点为红色,就需要进行平衡调整。
红黑树的操作过程需要经常性地访问节点的父节点,因而相对于二叉树的节点结构,这里需要增加指向父节点的指针。同时,红黑树的节点还需要存储该节点的颜色值。这里我还加了子节点位置的定义,可以方便我们获取节点的兄弟节点以及判断节点属于外侧还是内侧。具体定义如下:
enum Color { RED, BLACK }; enum ChildType { NONE, LEFT, RIGHT }; struct RBTreeNode:BTreeNode { RBTreeNode():BTreeNode(),parent(NULL),color(RED){} RBTreeNode(int data,RBTreeNode* leftChild = NULL,RBTreeNode* rightChild = NULL) :BTreeNode(data,leftChild,rightChild),parent(NULL),color(RED) { } RBTreeNode*& getLeftChild() { return (RBTreeNode*&)leftChild; } RBTreeNode*& getRightChild() { return (RBTreeNode*&)rightChild; } RBTreeNode*& getChildByType(ChildType type) { return type == LEFT?getLeftChild():getRightChild(); } RBTreeNode* getSibling() { ChildType type = getChildType(); switch (type) { case LEFT: return (RBTreeNode*)parent->rightChild; case RIGHT: return (RBTreeNode*)parent->leftChild; default: break; } return NULL; } ChildType getChildType() { if (parent != NULL) { return data < parent->data?LEFT:RIGHT; } return NONE; } void handleChildren() { if (getLeftChild() != NULL) { getLeftChild()->parent = this; } if (getRightChild() != NULL) { getRightChild()->parent = this; } } RBTreeNode* parent; Color color; };红黑树与AVL树一样,需要借助旋转操作进行树的平衡,但红黑树的节点还定义了指向父节点的指针,这个在旋转过程中也需要对应调整,具体实现如下:
void RBTree::rotateWithLeftChild(RBTreeNode* node) { RBTreeNode* leftChild = node->getLeftChild(); node->leftChild = leftChild->rightChild; if (leftChild->rightChild != NULL) { leftChild->getRightChild()->parent = node; } leftChild->parent = node->parent; if (node->getChildType() == NONE) { m_pRoot = leftChild; } else if (node->getChildType() == LEFT) { node->parent->leftChild = leftChild; } else { node->parent->rightChild = leftChild; } leftChild->rightChild = node; node->parent = leftChild; } void RBTree::rotateWithRightChild(RBTreeNode* node) { RBTreeNode* rightChild = (RBTreeNode*)node->rightChild; node->rightChild = rightChild->leftChild; if (rightChild->leftChild != NULL) { rightChild->getLeftChild()->parent = node; } rightChild->parent = node->parent; if (node->getChildType() == NONE) { m_pRoot = rightChild; } else if (node->getChildType() == LEFT) { node->parent->leftChild = rightChild; } else { node->parent->rightChild = rightChild; } rightChild->leftChild = node; node->parent = rightChild; } void RBTree::doubleWithLeftChild(RBTreeNode* node) { rotateWithRightChild(node->getLeftChild()); rotateWithLeftChild(node); } void RBTree::doubleWithRightChild(RBTreeNode* node) { rotateWithLeftChild(node->getRightChild()); rotateWithRightChild(node); }红黑树的查找操作与二叉查找树的查找操作一致,直接复用。
红黑树的插入算法与二叉查找树的插入算法一致,但插入节点后,需要根据不同的情况进行不同的操作,插入情况分以下几点: 1. 父节点为空。插入节点的父节点为空,则可知插入节点为根节点,根据规则2将其设置为黑色; 2. 黒父。插入节点的父节点为黑色,此时没有违反任何规则,无需进行平衡处理; 3. 红父红叔。插入节点的父节点和叔父节点的颜色为红色,根据规则可知祖父节点必然为黑色。此时无需旋转,只用将祖父节点设置为红色,将父节点和叔父节点的颜色设置为黑色,祖父节点达到平衡。但是祖父节点可能为根节点或者引起其父节点不平衡,因而需要将祖父节点作为插入节点进行平衡处理; 4. 红父黑叔。插入节点的父节点为红色,叔父节点为空或者黑色,根据规则可知祖父节点必然为黑色。此时根据插入节点的插入位置分为外侧插入和内侧插入: 4.1外侧插入。若插入节点和父节点都是其对应父节点的左子女或者右子女,则为外侧插入。此时将祖父节点设置为红色,父节点的颜色设置为黑色,然后对祖父节点进行单旋转,使祖父节点成为父节点的子节点,达到平衡; 4.2内侧插入。若插入节点和父节点处于其对应父节点的不同子女位置,则为内侧插入。此时将祖父节点设置为红色,插入节点的颜色设置为黑色,然后对祖父节点进行双旋转,使祖父节点成为插入节点的子节点,达到平衡。 以下为插入算法和平衡算法的具体实现:
void RBTree::_insert(int key, BTreeNode* &node) { RBTreeNode* &curNode = (RBTreeNode* &)node; if (curNode == NULL) { curNode = new RBTreeNode(key); balanceTreeNode(curNode); } else { if (key < curNode->data) { if (curNode->leftChild != NULL) { _insert(key, node->leftChild); } else { curNode->leftChild = new RBTreeNode(key); curNode->getLeftChild()->parent = curNode; balanceTreeNode(curNode->getLeftChild()); } } else if (key > curNode->data) { if (curNode->rightChild != NULL) { _insert(key, node->rightChild); } else { curNode->rightChild = new RBTreeNode(key); curNode->getRightChild()->parent = curNode; balanceTreeNode(curNode->getRightChild()); } } else { } } } void RBTree::balanceTreeNode(RBTreeNode* node) { if (node == NULL) { return; } //插入节点为根节点,根节点必须为黑色 if (node->parent == NULL) { node->color = BLACK; return; } //插入节点的父节点为黑色,不破坏平衡 if (node->parent->color == BLACK) { return; } RBTreeNode* parent = node->parent; RBTreeNode* grand = parent->parent; RBTreeNode* uncle = parent->getSibling(); //伯父节点为空或者黑色 if(uncle == NULL || uncle->color == BLACK) { if(node->getChildType() == parent->getChildType()) { //外侧插入 grand->color = RED; parent->color = BLACK; if(node->getChildType() == LEFT) { rotateWithLeftChild(grand); } else { rotateWithRightChild(grand); } } else { //内侧插入 grand->color = RED; node->color = BLACK; if(node->getChildType() == LEFT) { doubleWithRightChild(grand); } else { doubleWithLeftChild(grand); } } } else { //无需旋转,直接改变颜色,然后对祖父节点进行平衡操作 grand->color = RED; parent->color = BLACK; uncle->color = BLACK; balanceTreeNode(grand); } }红黑树的删除算法与二叉查找树的删除算法一致,因此删除的节点必然为单支节点或者叶子节点。但删除节点后,需要进行平衡检测,删除情况分为以下几种: 1. 删除节点为红色。此时根据红黑树规则,删除节点必然为叶子节点,删除后不破坏平衡规则,直接删除即可; 2. 删除节点为黑色。删除该节点后,会选子节点作为替代节点。若替代节点为红色,此时所有经过子节点的路径上的黑色节点的数目都减少了一个,只需将子节点的颜色设置为黑色即可达到平衡; 3. 替换节点为空或者黑色。根据红黑树规则,黑色节点只有一个黑色子节点的情况是不存在的。同时,若删除节点不为根节点,则其兄弟节点必然不为空。此时又有以下几种情况: 3.1替换节点成为根节点。此时不做处理; 3.2红兄。删除节点的兄弟节点为红色,此时父节点的颜色为黑色,兄弟节点的子节点也必然为黑色,将父节点的颜色设置为红色,兄弟节点的颜色设置为黑色,并对父节点进行单旋转,使父节点成为兄弟节点的子节点,转换为黑兄情况; 3.3黑兄二黑侄。删除节点的兄弟节点为黑色,同时兄弟节点没有子节点或者有两个黑色子节点,此时将兄弟节点的颜色设置为红色,父节点达到平衡。但是父节点的颜色不确定,需要把父节点作为新的替换节点进行平衡处理; 3.4黑兄内侧红侄。此时设置兄弟节点的颜色为红色,红侄的颜色设置为黑色,并对兄弟节点进行单旋操作,使红侄成为新的兄弟节点,转为下一种情况; 3.5黑兄外侧红侄。此时设置兄弟节点的颜色为父节点的颜色,设置父节点的颜色为黑色,设置红侄节点的颜色为黑色,然后对父节点进行单旋操作,使兄弟节点成为新的父节点,达到平衡。 以下是删除算法和删除平衡算法的具体实现:
void RBTree::_remove(int key, BTreeNode* &node) { if (node == NULL) { return; } if (key == node->data) { if (node->leftChild != NULL && node->rightChild != NULL) { //替换键值 BTreeNode * temp = _findMin(node->rightChild); node->data = temp->data; //递归删除 _remove(temp->data,node->rightChild); } else { RBTreeNode* &curNode = (RBTreeNode* &)node; RBTreeNode* temp = curNode; RBTreeNode* parent = curNode->parent; RBTreeNode* sibling = curNode->getSibling(); curNode = (curNode->getLeftChild() != NULL)?curNode->getLeftChild():curNode->getRightChild(); if (curNode != NULL) { curNode->parent = parent; } if (temp->color == RED) { //红色节点直接删除 delete temp; } else { balanceTreeNodeRemove(curNode,parent); delete temp; } } } else if (key < node->data) { _remove(key,node->leftChild); } else { _remove(key,node->rightChild); } } void RBTree::balanceTreeNodeRemove(RBTreeNode* node, RBTreeNode* parent) { while (true) { //根节点,不再处理 if (node == m_pRoot) { break; } //红色节点,直接变为黑色 if (node != NULL && node->color == RED) { node->color = BLACK; break; } RBTreeNode* sibling = parent->getLeftChild() == node? parent->getRightChild():parent->getLeftChild(); if (sibling->color == RED) { //兄弟节点为红色,转为兄弟节点为黑色的情况 parent->color = RED; sibling->color = BLACK; if (sibling->getChildType() == LEFT) { rotateWithLeftChild(parent); } else { rotateWithRightChild(parent); } //重新获取兄弟节点 sibling = parent->getLeftChild() == node? parent->getRightChild():parent->getLeftChild(); } //黑兄二黑侄 if ((sibling->leftChild == NULL || sibling->getLeftChild()->color == BLACK) && (sibling->rightChild == NULL || sibling->getRightChild()->color == BLACK)) { //黑兄二黑侄红父或黑兄二黑侄黑父 sibling->color = RED; node = parent; parent = node->parent; } else { ChildType type = sibling->getChildType() == LEFT?RIGHT:LEFT; RBTreeNode* child = sibling->getChildByType(type); //黑兄内侧红侄 if (child != NULL && child->color == RED) { sibling->color = RED; if (type == LEFT) { sibling->getLeftChild()->color = BLACK; rotateWithLeftChild(sibling); sibling = sibling->getLeftChild(); } else { sibling->getRightChild()->color = BLACK; rotateWithRightChild(sibling); sibling = sibling->getRightChild(); } } //黑兄外侧红侄 sibling->color = parent->color; parent->color = BLACK; type = sibling->getChildType(); if (type == LEFT) { if (sibling->getLeftChild() != NULL) { sibling->getLeftChild()->color = BLACK; } rotateWithLeftChild(parent); } else { if (sibling->getLeftChild() != NULL) { sibling->getLeftChild()->color = BLACK; } rotateWithRightChild(parent); } break; } } }红黑树的清理操作与二叉树的清理操作一致,直接复用。
这里对红黑树进行具体的测试,代码如下:
RBTree* rbTree = new RBTree(); rbTree->insert(3); rbTree->insert(2); rbTree->insert(4); rbTree->insert(1); rbTree->insert(5); rbTree->insert(6); rbTree->insert(7); rbTree->insert(8); rbTree->insert(9); cout<<"contains 5:"<<rbTree->contains(5)<<endl; //contains 5:1 cout<<"contains 10:"<<rbTree->contains(10)<<endl; //contains 10:0 rbTree->breadthFirstTravel(); //bft:5 3 7 2 4 6 8 1 9 rbTree->depthFirstTravel_DLR(); //depthFirstTravel_DLR:5(b) 3(r) 2(b) 1(r) 4(b) 7(r) 6(b) 8(b) 9(r) rbTree->depthFirstTravel_LDR(); //depthFirstTravel_LDR:1 2 3 4 5 6 7 8 9 rbTree->depthFirstTravel_LRD(); //depthFirstTravel_LRD:1 2 4 3 6 9 8 7 5 rbTree->remove(7); rbTree->breadthFirstTravel(); //bft:5 3 8 2 4 6 9 1 rbTree->depthFirstTravel_DLR(); //depthFirstTravel_DLR:5(b) 3(r) 2(b) 1(r) 4(b) 8(r) 6(b) 9(b) rbTree->depthFirstTravel_LDR(); //depthFirstTravel_LDR:1 2 3 4 5 6 8 9 rbTree->depthFirstTravel_LRD(); //depthFirstTravel_LRD:1 2 4 3 6 9 8 5 rbTree->clear(); rbTree->breadthFirstTravel(); //bft:红黑树与AVL树效率一致,但其平衡性没有AVL树那么严格,能够在保持平衡的情况下出现更少需要进行平衡调整的情况,因而拥有更好的统计性能。