赞
踩
红黑树和BST树、AVL树一样,都是带有排序性质的树。那么与这两种树不同的地方在哪?为什么在C++STL中的set和map都使用的红黑树?
本文将用易于理解的描述,使得每个人都能看懂红黑树中的调整操作。
因为每个节点不是红色就是黑色,所以需要定义一个颜色相关的枚举量。
还需要操作其父节点,所以定义一个parent指针。
template<typename _Ty> class RBTree { private: // 节点颜色 enum Color { BLACK, RED }; // 节点定义 struct Node { Node(_Ty data = _Ty(), Node* left = nullptr, Node* right = nullptr, Node* parent = nullptr, Color color = BLACK) : val_(data) , left_(left) , right_(right) , parent_(parent) , color_(color) {} _Ty val_; Node* left_; Node* right_; Node* parent_; // 指向当前节点的父节点 Color color_; // 当前节点的颜色 }; Node* root_; };
此时要检查父节点颜色,如果父节点是黑色,插入完成;
如果父节点是红色,则出现连续的红色节点,就要开始做红黑树的插入调整操作:
父节点是红色,爷爷节点是黑色,且三者在同一侧。
如图新插入的节点C,如果父节点是红色,则为了保持红黑树的第4条性质,需要交换父亲节点和爷爷节点的颜色:
从局部图来看,这样似乎没错,但考虑不周,在这里还需要考虑爷爷节点的另一个孩子节点,这里叫作叔叔节点,如果叔叔节点本身为红色,那么经过这样交换后,又不满足红黑树性质了。所以正确的情况为:
即使到了这一步,也不要觉得完成了,我们把新节点的爷爷节点的颜色改成红色了,这时候还需要考虑两种情况
插入节点的父节点是红色,爷爷节点是黑色,三者在同一侧,叔叔节点是黑色。
为了满足红黑树性质,需要将其父节点颜色与爷爷节点颜色进行交换。
出现的问题:将爷爷节点A改成红色,那么就会导致从根节点到节点D的这条路径中,少一个黑色节点。不满足红黑树第5条性质(所有路径的黑色节点数量相等)。
解决方案:以A节点为参数做右旋转操作,旋转完成后,符合红黑树的性质,调整完成。
父节点是红色,爷爷节点是黑色,三者不在一侧,叔叔节点是黑色。
如图:如果直接调整颜色,那么还是会影响红黑树的第5个性质,所以可以先将三者旋转成同一侧,显然,这里需要对父节点B做左旋转,旋转后如图
将其旋转完成后,ABC处于同一侧,这时候再说如何改变颜色,但通过观察,旋转完成后,变成和情况2是一个情况,所以直接让其转到情况2的操作:
上面的三种情况,都是插入的节点在爷爷节点的左孩子上,那么与之对应的,右边也有相应的三种情况,所以红黑树的插入调整,一共有六种调整。
总结:红黑树的插入最重要的是考虑叔叔节点的颜色。
// 返回节点颜色 Color getColor(Node* node) { return node == nullptr ? BLACK : node->color_; } // 设置颜色 void setColor(Node* node, Color color) { node->color_ = color; } // 返回节点的左孩子 Node* getLeft(Node* node) { return node->left_; } // 返回节点的右孩子 Node* getRight(Node* node) { return node->right_; } // 返回节点的父节点 Node* getParent(Node* node) { return node->parent_; }
如图,对node进行左旋转操作,它和AVL树的左旋转有什么区别?
AVL树左旋转代码:
// 左旋转操作
void leftRotate(Node* node)
{
Node* child = node->right_;
node->right_ = child->left_;
child->left_ = node;
}
由于红黑树多出了parent指针,所以在旋转时,要更改六个地址:
加上对parent操作的代码:
// 左旋转操作 void leftRotate(Node* node) { Node* child = node->right_; child->parent_ = node->parent_; if (node->parent_ == nullptr) { root_ = child; } else { if (node->parent_->left_ == node) { // node在父节点的左孩子 node->parent_->left_ = child; } else { // node在父节点的右孩子 node->parent_->right_ = child; } } node->right_ = child->left_; if (child->left_ != nullptr) { child->left_->parent_ = node; } child->left_ = node; node->parent_ = child; }
代码实现和左旋转刚好相反
// 右旋转 void rightRotate(Node* node) { Node* child = node->left_; child->parent_ = node->parent_; if (node->parent_ == nullptr) { // node原来就是root节点 root_ = child; } else { if (node->parent_->left_ == node) { node->parent_->left_ = child; } else { node->parent_->right_ = child; } } node->left_ = child->right_; if (child->right_ != nullptr) { child->right_->parent_ = node; } child->right_ = node; node->parent_ = child; }
基于BST树的插入代码
// 插入操作 void insert(const _Ty& data) { if (root_ == nullptr) // 1 { root_ = new Node(data); return; } Node* parent = nullptr; // 2 Node* cur = root_; // 3 while (cur != nullptr) { if (cur->val_ > data) // 4 { parent = cur; cur = cur->left_; } else if (cur->val_ < data) // 5 { parent = cur; cur = cur->right_; } else { return; // 遇到重复的,则什么都不做 // 6 } } // 设置当前节点的parent和颜色 Node* node = new Node(data, nullptr, nullptr, parent, RED); // 7 if (parent->val_ > data) { parent->left_ = node; } else { parent->right_ = node; } // 如果新插入的红色节点,父节点也是红色, // 则不满足红黑树性质,进行插入调整操作 if (RED == getColor(parent)) // 8 { fixAfterInsert(node); } }
该函数的作用就是在进行插入完成后,为了保证红黑树性质,所做出的调整(修正)操作,涉及更改颜色,旋转操作。
// 红黑树的插入调整操作 void fixAfterInsert(Node* node) { // 如果当前红色节点的父节点也是红色 while (RED == getColor(getParent(node))) // 1 { // 如果当前节点的父节点是爷爷节点的左孩子 if (getLeft(getParent(getParent(node))) == getParent(node)) // 2 { // 插入的节点在左子树 // 找到叔叔节点,改变颜色 Node* uncle = getRight(getParent(getParent(node))); // 3 if (RED == getColor(uncle)) // 情况1:叔叔节点是红色 { setColor(uncle, BLACK); setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); // 继续调整上面的 node = getParent(getParent(node)); // 4 } else { // 先处理情况三,转成情况二 if (getRight(getParent(node)) == node) // 5 { node = getParent(node); leftRotate(node); } // 6 处理情况二 setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); rightRotate(getParent(getParent(node))); break; // 调整完成 } } else // 7 { // 插入的节点在右子树 Node* uncle = getLeft(getParent(getParent(node))); if (RED == getColor(uncle)) // 情况1:叔叔节点是红色 { setColor(uncle, BLACK); setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); // 继续调整上面的 node = getParent(getParent(node)); } else { // 先处理情况三,转成情况二 if (getLeft(getParent(node)) == node) { node = getParent(node); rightRotate(node); } // 处理情况二 setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); leftRotate(getParent(getParent(node))); break; // 调整完成 } } } // 强制root_为黑色 setColor(root_, BLACK); // 8 }
这里可以配合上面的图片来理解:
红黑树的删除,按找孩子节点划分,可分为:
按照删除节点的颜色划分,可分为:
删除的节点在父节点左边,右边的兄弟是黑色,兄弟节点的右孩子节点是红色。
如图,删除的B节点是黑色节点,为了保证红黑树的性质,需要从兄弟节点C借一个黑色节点,这时候需要C节点的孩子是红色,因为可以将其染成黑色,来保证红黑树的性质。
解决方案:
(1)交换父节点A和兄弟节点C的颜色;
(2)把兄弟节点的孩子节点置为黑色;
(3)对父节点A做一个左旋转操作。
删除的节点在父节点左边,右边的兄弟节点是黑色,兄弟节点的左孩子是红色。
如下图,这种情况下不能像情况1那样,直接从兄弟节点借调一个,因为兄弟节点C右边的路径上没有红色节点用以涂成黑色。
解决方案:
(1)对兄弟节点进行右旋转,使得C的左孩子和C在一条路径上,得到下图中的图2;
(2)为了保证红黑树性质,需要将D和C节点的颜色交换(A可能为红色,不能出现连续红色);
(3)接下来是不是很熟悉,就是转换成了上面的情况1;
删除节点是父节点左孩子,兄弟节点是黑色,兄弟节点的两个孩子节点都是黑色。
这种情况下,不能从兄弟节点那里借调。可以选择的方法是:直接将兄弟节点染成红色。
将兄弟节点涂成红色后,还要检查父节点颜色,如果是红色,则出现连续红色,需要调整,将其父节点染成黑色;
如果是黑色,则向上回溯,查看是否是其他情况。
待删除节点是左孩子,兄弟节点是红色。
调整目的还是和上面一样,先给待删除节点找一个黑色兄弟节点。
解决方案:
(1)将兄弟节点C和父节点A的颜色进行交换;
(2)对父节点A做左旋转操作,这样就会给待删除节点B匹配一个黑色的兄弟节点;
(3)然后转换为上面三种情况。
有上面的四种情况,那就有其镜像的情况,所以删除调整操作一共有8种情况。
总结:删除最重要的是看兄弟节点颜色。
同样是基于BST树的删除:
// 删除操作 void remove(const _Ty& data) { if (root_ == nullptr) // 1 { return; } Node* cur = root_; // 2 while (cur != nullptr) { if (cur->val_ > data) { cur = cur->left_; } else if (cur->val_ < data) { cur = cur->right_; } else { break; } } // 没找到节点,返回 // 3 if (cur == nullptr) { return; } // 删除前驱节点 情况三 // 4 if (getLeft(cur) != nullptr && getRight(cur) != nullptr) { Node* pre = cur->left_; // 5 while (getParent(pre) != nullptr) { pre = pre->right_; } cur->val_ = pre->val_; cur = pre; // cur指向前驱节点 } // 删除cur指向的节点 情况一和二 // 6 Node* child = cur->left_; // 让child指向不为空的孩子 if (child == nullptr) { child = cur->right_; } // left right parent if (child != nullptr) // 7 { child->parent_ = cur->parent_; if (getParent(cur) == nullptr) { root_ = child; // 8 } else // 9 { if (getLeft(getParent(cur)) == cur) { cur->parent_->left_ = child; } else { cur->parent_->right_ = child; } } Color c = getColor(cur); // 10 delete cur; // 如果删除的黑色节点,需要调整到满足红黑树性质 // 11 if (c == BLACK) { fixAfterRemove(child); } } else // child == nullptr { if (getParent(cur) == nullptr) // 12 { delete cur; root_ = nullptr; return; } else { // 删除的cur是叶子节点 if (BLACK == getColor(cur)) //13 { fixAfterRemove(cur); } if (getLeft(getParent(cur)) == cur) // 14 { cur->parent_->left_ = nullptr; } else { cur->parent_->right_ = nullptr; } delete cur; } } }
调整操作一共八种情况,左右各四种,理论讲解完后,现在实现代码:
// 红黑树的删除调整操作 void fixAfterRemove(Node* node) { while (getColor(node) == BLACK) // 1 { if (getLeft(getParent(node)) == node) // 2 { // 删除的黑色节点在左子树 Node* brother = getRight(getParent(node)); // 3 if (getColor(brother) == RED) // 情况四 // 4 { // 交换父节点和兄弟节点颜色 setColor(getParent(node), RED); setColor(brother, BLACK); leftRotate(getParent(node)); brother = getRight(parent(node)); } // 兄弟是黑色 // 5 if (getColor(getLeft(brother)) == BLACK && getColor(getRight(brother)) == BLACK) // 情况三 // 6 { setColor(brother, RED); node = getParent(node); } else { if (getColor(getRight(brother)) != RED) // 情况二 // 7 { setColor(brother, RED); setColor(getLeft(brother), BLACK); rightRotate(brother); brother = getRight(getParent(node)); } // 归结为情况一 // 8 setColor(brother, getColor(getParent(node))); setColor(getParent(node), BLACK); setColor(getRight(brother), BLACK); leftRotate(getParent(node)); break; } } else // 9 { // 删除的黑色节点在右子树 Node* brother = getLeft(getParent(node)); if (getColor(brother) == RED) { // 交换父节点和兄弟节点颜色 setColor(getParent(node), RED); setColor(brother, BLACK); rightRotate(getParent(node)); brother = getLeft(getParent(node)); } // 兄弟是黑色 if (getColor(getLeft(brother)) == BLACK && getColor(getRight(brother)) == BLACK) // 情况三 { setColor(brother, RED); node = getParent(node); } else { if (getColor(getLeft(brother)) != RED) // 情况二 { setColor(brother, RED); setColor(getRight(brother), BLACK); leftRotate(brother); brother = getLeft(getParent(node)); } // 归结为情况一 setColor(brother, getColor(getParent(node))); setColor(getParent(node), BLACK); setColor(getLeft(brother), BLACK); rightRotate(getParent(node)); break; } } } // 如果发现node指向的节点是红色,直接涂成黑色,调整结束 setColor(node, BLACK); // 10 }
#include <iostream> #include <algorithm> using namespace std; template<typename _Ty> class RBTree { public: RBTree() :root_(nullptr) {} // 插入操作 void insert(const _Ty& data) { if (root_ == nullptr) { root_ = new Node(data); return; } Node* parent = nullptr; Node* cur = root_; while (cur != nullptr) { if (cur->val_ > data) { parent = cur; cur = cur->left_; } else if (cur->val_ < data) { parent = cur; cur = cur->right_; } else { return; // 遇到重复的,则什么都不做 } } // 设置当前节点的parent和颜色 Node* node = new Node(data, nullptr, nullptr, parent, RED); if (parent->val_ > data) { parent->left_ = node; } else { parent->right_ = node; } // 如果新插入的红色节点,父节点也是红色, // 则不满足红黑树性质,进行插入调整操作 if (RED == getColor(parent)) { fixAfterInsert(node); } } // 删除操作 void remove(const _Ty& data) { if (root_ == nullptr) { return; } Node* cur = root_; while (cur != nullptr) { if (cur->val_ > data) { cur = cur->left_; } else if (cur->val_ < data) { cur = cur->right_; } else { break; } } // 没找到节点,返回 if (cur == nullptr) { return; } // 删除前驱节点 情况三 if (getLeft(cur) != nullptr && getRight(cur) != nullptr) { Node* pre = cur->left_; while (getRight(pre) != nullptr) { pre = pre->right_; } cur->val_ = pre->val_; cur = pre; // cur指向前驱节点 } // 删除cur指向的节点 情况一和二 Node* child = cur->left_; // 让child指向不为空的孩子 if (child == nullptr) { child = cur->right_; } // left right parent if (child != nullptr) { child->parent_ = cur->parent_; if (getParent(cur) == nullptr) { root_ = child; } else { if (getLeft(getParent(cur)) == cur) { cur->parent_->left_ = child; } else { cur->parent_->right_ = child; } } Color c = getColor(cur); delete cur; // 如果删除的黑色节点,需要调整到满足红黑树性质 if (c == BLACK) { fixAfterRemove(child); } } else // child == nullptr { if (getParent(cur) == nullptr) { delete cur; root_ = nullptr; return; } else { // 删除的cur是叶子节点 if (BLACK == getColor(cur)) { fixAfterRemove(cur); } if (getLeft(getParent(cur)) == cur) { cur->parent_->left_ = nullptr; } else { cur->parent_->right_ = nullptr; } delete cur; } } } private: // 节点颜色 enum Color { BLACK, RED }; // 节点定义 struct Node { Node(_Ty data = _Ty(), Node* left = nullptr, Node* right = nullptr, Node* parent = nullptr, Color color = BLACK) : val_(data) , left_(left) , right_(right) , parent_(parent) , color_(color) {} _Ty val_; Node* left_; Node* right_; Node* parent_; // 指向当前节点的父节点 Color color_; // 当前节点的颜色 }; // 返回节点颜色 Color getColor(Node* node) { return node == nullptr ? BLACK : node->color_; } // 设置颜色 void setColor(Node* node, Color color) { node->color_ = color; } // 返回节点的左孩子 Node* getLeft(Node* node) { return node->left_; } // 返回节点的右孩子 Node* getRight(Node* node) { return node->right_; } // 返回节点的父节点 Node* getParent(Node* node) { return node->parent_; } // 左旋转操作 void leftRotate(Node* node) { Node* child = node->right_; child->parent_ = node->parent_; if (node->parent_ == nullptr) { root_ = child; } else { if (node->parent_->left_ == node) { // node在父节点的左孩子 node->parent_->left_ = child; } else { // node在父节点的右孩子 node->parent_->right_ = child; } } node->right_ = child->left_; if (child->left_ != nullptr) { child->left_->parent_ = node; } child->left_ = node; node->parent_ = child; } // 右旋转 void rightRotate(Node* node) { Node* child = node->left_; child->parent_ = node->parent_; if (node->parent_ == nullptr) { // node原来就是root节点 root_ = child; } else { if (node->parent_->left_ == node) { node->parent_->left_ = child; } else { node->parent_->right_ = child; } } node->left_ = child->right_; if (child->right_ != nullptr) { child->right_->parent_ = node; } child->right_ = node; node->parent_ = child; } // 红黑树的插入调整操作 void fixAfterInsert(Node* node) { // 如果当前红色节点的父节点也是红色 while (RED == getColor(getParent(node))) { // 如果当前节点的父节点是爷爷节点的左孩子 if (getLeft(getParent(getParent(node))) == getParent(node)) { // 插入的节点在左子树 // 找到叔叔节点,改变颜色 Node* uncle = getRight(getParent(getParent(node))); if (RED == getColor(uncle)) // 情况1:叔叔节点是红色 { setColor(uncle, BLACK); setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); // 继续调整上面的 node = getParent(getParent(node)); } else { // 先处理情况三,转成情况二 if (getRight(getParent(node)) == node) { node = getParent(node); leftRotate(node); } // 处理情况二 setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); rightRotate(getParent(getParent(node))); break; // 调整完成 } } else { // 插入的节点在右子树 Node* uncle = getLeft(getParent(getParent(node))); if (RED == getColor(uncle)) // 情况1:叔叔节点是红色 { setColor(uncle, BLACK); setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); // 继续调整上面的 node = getParent(getParent(node)); } else { // 先处理情况三,转成情况二 if (getLeft(getParent(node)) == node) { node = getParent(node); rightRotate(node); } // 处理情况二 setColor(getParent(node), BLACK); setColor(getParent(getParent(node)), RED); leftRotate(getParent(getParent(node))); break; // 调整完成 } } } // 强制root_为黑色 setColor(root_, BLACK); } // 红黑树的删除调整操作 void fixAfterRemove(Node* node) { while (getColor(node) == BLACK) { if (getLeft(getParent(node)) == node) { // 删除的黑色节点在左子树 Node* brother = getRight(getParent(node)); if (getColor(brother) == RED) // 情况四 { // 交换父节点和兄弟节点颜色 setColor(getParent(node), RED); setColor(brother, BLACK); leftRotate(getParent(node)); brother = getRight(parent(node)); } // 兄弟是黑色 if (getColor(getLeft(brother)) == BLACK && getColor(getRight(brother)) == BLACK) // 情况三 { setColor(brother, RED); node = getParent(node); } else { if (getColor(getRight(brother)) != RED) // 情况二 { setColor(brother, RED); setColor(getLeft(brother), BLACK); rightRotate(brother); brother = getRight(getParent(node)); } // 归结为情况一 setColor(brother, getColor(getParent(node))); setColor(getParent(node), BLACK); setColor(getRight(brother), BLACK); leftRotate(getParent(node)); break; } } else { // 删除的黑色节点在右子树 Node* brother = getLeft(getParent(node)); if (getColor(brother) == RED) // 情况四 { // 交换父节点和兄弟节点颜色 setColor(getParent(node), RED); setColor(brother, BLACK); rightRotate(getParent(node)); brother = getLeft(getParent(node)); } // 兄弟是黑色 if (getColor(getLeft(brother)) == BLACK && getColor(getRight(brother)) == BLACK) // 情况三 { setColor(brother, RED); node = getParent(node); } else { if (getColor(getLeft(brother)) != RED) // 情况二 { setColor(brother, RED); setColor(getRight(brother), BLACK); leftRotate(brother); brother = getLeft(getParent(node)); } // 归结为情况一 setColor(brother, getColor(getParent(node))); setColor(getParent(node), BLACK); setColor(getLeft(brother), BLACK); rightRotate(getParent(node)); break; } } } // 如果发现node指向的节点是红色,直接涂成黑色,调整结束 setColor(node, BLACK); } Node* root_; // 红黑树的根节点 }; int main(void) { RBTree<int> rbtree; for (int i = 1; i <= 4; ++i) { rbtree.insert(i); } return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。