红黑树的实现

上一篇中我们讲述了红黑树的插入, 以及删除时需要进行的各种调整的情况, 根据这些情况, 我们可以用代码实现红黑树的插入与删除操作.

节点的定义#

一颗红黑树的定义如下:

Copy
// 定义颜色枚举类型 enum Color { RED, BLACK }; template <class T> struct Red_Black_Node { Red_Black_Node* left; Red_Black_Node* right; Red_Black_Node* parent; //记录坐标, 用于打印 int x, y; T value; // 节点的值, 红黑树中节点的值是必须可以改变的 Color node_color; // 节点的颜色 bool NIL; // 该节点是否是 NIL 节点 Red_Black_Node(T value, Color node_color, bool NIL); // 构造函数 void Set_Color(Color new_color); void SetValue(T new_value); // 将当前节点的父节点指向新的对应的孩子节点 new_child void SetNewChild(Red_Black_Node* new_child); Red_Black_Node* LeftRotate(); Red_Black_Node* RightRotate(); Red_Black_Node* GetUncle(); Red_Black_Node* GetSibling(); Red_Black_Node* GetCloseNephew(); Red_Black_Node* GetFarNephew(); };

为了在删除操作中简单化, 我们在节点的旋转与调整中引入了一些额外的操作, 例如 SetNewChild 函数, 表示替换当前节点的父节点的孩子节点为新的节点, 在后续删除当前节点的时候十分有用, 实现如下:

Copy
template <class T> void Red_Black_Node<T>::SetNewChild(Red_Black_Node* new_child) { if (this->parent == nullptr) { return; } if (this == this->parent->left) { this->parent->left = new_child; } else { this->parent->right = new_child; } new_child->parent = this->parent; }

还有, 在旋转的操作中, 我们也将旋转后的根节点的与父节点进行了连接, 实现如下:

Copy
// 以当前节点为根节点进行左旋转 template <class T> Red_Black_Node<T>* Red_Black_Node<T>::LeftRotate() { Red_Black_Node* temp_parent = this->parent; Red_Black_Node* temp = this->right; this->right->left->parent = this; this->right = this->right->left; this->parent = temp; temp->left = this; if (temp_parent != nullptr) { if (this == temp_parent->left) { temp_parent->left = temp; } else { temp_parent->right = temp; } temp->parent = temp_parent; } else { temp->parent = nullptr; } return temp; }

这些操作极大的简化了后续插入与删除的过程中的调整操作.

红黑树的插入操作#

红黑树也是二叉查找树的一种, 其插入的开始步骤与普通的二叉查找树相同, 只有在插入这个节点之后, 才需要对树的结构进行调整, 插入操作的整体步骤如下:

Copy
template <class T> void Red_Black_Tree<T>::insert(T value) { Red_Black_Node<T>* direct = this->root; Red_Black_Node<T>* pre_direct = this->root; // 找到插入的位置 while (direct != nullptr && direct->value != value && !direct->NIL) { pre_direct = direct; if (direct->value < value) { direct = direct->right; } else { direct = direct->left; } } // 新建插入的节点 Red_Black_Node<T>* new_node = new Red_Black_Node<T>(value, Color::RED, false); // 如果是空树, 设置新建节点为根节点 if (this->root == nullptr) { // 根节点为黑色 new_node->Set_Color(Color::BLACK); this->root = new_node; Red_Black_Node<T>* new_left_node = new Red_Black_Node<T>(0, Color::BLACK, true); Red_Black_Node<T>* new_right_node = new Red_Black_Node<T>(0, Color::BLACK, true); this->root->left = new_left_node; this->root->right = new_right_node; return; } // 如果这个节点不是NIL节点, 该值已存在 if (!direct->NIL) { return; } // 在正确的位置插入新建的节点 if (direct == pre_direct->left) { Red_Black_Node<T>* new_right_node = new Red_Black_Node<T>(0, Color::BLACK, true); new_node->left = pre_direct->left; new_node->right = new_right_node; pre_direct->left = new_node; } else { Red_Black_Node<T>* new_left_node = new Red_Black_Node<T>(0, Color::BLACK, true); new_node->right = pre_direct->right; new_node->left = new_left_node; pre_direct->right = new_node; } new_node->parent = pre_direct; // 插入节点之后调整红黑树的结构 InsertBalanceAdjust(new_node); }

红黑树结构的调整#

将一个节点插入到红黑树中, 需要对树的结构进行调整, 按照 上一篇博客 所示, 我们列出了各种不同的情况下需要进行的调整操作, 这些情况如下:

Copy
template <class T> int Red_Black_Tree<T>::InsertBalanceAction(Red_Black_Node<T>* node) { // 走到根节点了, 将该节点设为根节点, 为黑色 if (node->parent == nullptr) { return 0; } // 如果当前节点的父节点存在并且为黑色 if (node->parent->node_color == Color::BLACK) { return 1; } // 如果父节点是红色 if (node->parent->node_color == Color::RED) { // 因为父亲节点是红色节点, 就一定不是根节点, 所以当前节点一定存在叔叔节点 // 如果叔叔节点为红色 if (node->GetUncle()->node_color == Color::RED) { return 2; } else { // 如果叔叔节点为黑色, 需要判断该节点的旋转方向 if (node == node->parent->left && node->parent == node->parent->parent->left) { return 3; } else if (node == node->parent->right && node->parent == node->parent->parent->right) { return 4; } else if (node == node->parent->right && node->parent == node->parent->parent->left) { return 5; } else if (node == node->parent->left && node->parent == node->parent->parent->right) { return 6; } return -1; } } return -1; }

然后是我们后续进行调整的步骤, 我们列出每种调整步骤如下:

Copy
template <class T> void Red_Black_Tree<T>::InsertBalanceAdjust(Red_Black_Node<T>* node) { Red_Black_Node<T>* temp_node = nullptr; switch (this->InsertBalanceAction(node)) { case 0: node->Set_Color(Color::BLACK); break; case 1: node->Set_Color(Color::RED); break; case 2: node->Set_Color(Color::RED); node->parent->Set_Color(Color::BLACK); node->GetUncle()->Set_Color(Color::BLACK); InsertBalanceAdjust(node->parent->parent); break; case 3: node->Set_Color(Color::RED); node->parent->Set_Color(Color::BLACK); node->parent->parent->Set_Color(Color::RED); temp_node = node->parent->parent->RightRotate(); if (temp_node->parent == nullptr) { this->root = temp_node; } break; case 4: node->Set_Color(Color::RED); node->parent->Set_Color(Color::BLACK); node->parent->parent->Set_Color(Color::RED); temp_node = node->parent->parent->LeftRotate(); if (temp_node->parent == nullptr) { this->root = temp_node; } break; case 5: node->Set_Color(Color::RED); // node->parent->parent->Set_Color(Color::RED); node->parent->parent->left = node->parent->LeftRotate(); // 实际问题被转换为case3 解决 InsertBalanceAdjust(node->left); break; case 6: node->Set_Color(Color::RED); node->parent->parent->Set_Color(Color::RED); node->parent->parent->right = node->parent->RightRotate(); // 实际问题被转换为case4 解决 InsertBalanceAdjust(node->right); default: break; } }

请注意在 case5case6中我们进行了转换, 但是转换之后, 需要调整的节点不是新插入的节点, 而是插入节点的孩子节点.

红黑树的删除操作#

与插入操作相同, 红黑树因为也是二叉查找树的一种, 所以节点删除的操作和普通的二叉查找树是一样的. 与平衡二叉树不同的是, 平衡二叉树会记录删除路径, 然后调整树的结构, 红黑树并不会直接删除需要删除的节点, 而是将这个节点值与后续节点的值进行替换. 然后删除叶子节点, 删除直接需要进行调整. 删除操作如下:

Copy
template <class T> void Red_Black_Tree<T>::erase(T value) { Red_Black_Node<T>* delete_node = nullptr; Red_Black_Node<T>* pre_delete_node = nullptr; Red_Black_Node<T>* find_node = this->root; // 先找到需要删除的节点的位置 while (find_node != nullptr && find_node->value != value && !find_node->NIL) { if (find_node->value < value) { find_node = find_node->right; } else { find_node = find_node->left; } } // 如果找不到要删除的节点, 那么直接返回, 要删除的节点不存在 if (find_node->NIL) { return; } // 找到该节点的直接后续节点 // 如果左子树或者右子树为空, 那么可以直接删除该节点, 将后续节点补上来 if (find_node->left->NIL || find_node->right->NIL) { delete_node = find_node; RemoveBalanceAdjust(delete_node); return; } // 找到当前节点的右子树节点 delete_node = find_node->right; // 找到右子树的最左边的叶子节点, 作为直接后续节点 while (!delete_node->left->NIL) { delete_node = delete_node->left; } find_node->SetValue(delete_node->value); RemoveBalanceAdjust(delete_node); return; }

删除后的结构调整#

红黑树删除节点之后, 我们使用与插入相同的方式判断需要进行调整的方式, 我们进行的分类如下:

Copy
template <class T> int Red_Black_Tree<T>::RemoveBalanceAction(Red_Black_Node<T>* node) { if (node->node_color == Color::RED) { return 1; } else { if ((node->left->NIL && !node->right->NIL && node->right->node_color == Color::RED) || (node->right->NIL && !node->left->NIL && node->left->node_color == Color::RED)) { return 2; } // 如果父节点不存在 if (node->parent == nullptr) { return 3; } // 如果父节点为红色 else if (node->parent->node_color == Color::RED) { // 父节点为红色一定存在邻近侄子节点, 或者邻近侄子节点为NIL节点, 黑色节点 if (node->GetCloseNephew()->node_color == Color::BLACK) { return 4; } if (node->GetCloseNephew()->node_color == Color::RED) { return 5; } } // 如果父节点为黑色 else { // 如果兄弟节点为红色, 进行一次转换后进入父节点为红色的场景 if (node->GetSibling()->node_color == Color::RED) { return 6; } // 如果兄弟节点是黑色 if (node->GetSibling()->node_color == Color::BLACK) { // 如果远侄子节点是红色, 需要进行一次旋转 if (node->GetFarNephew()->node_color == Color::RED) { return 7; } else { // 如果兄弟节点是黑色, 邻近侄子节点是红色 if (node->GetCloseNephew()->node_color == Color::RED) { return 8; } else { // 如果兄弟节点是黑色, 邻近侄子节点是黑色 return 9; } } } } } return -1; }

对每一种情况, 我们又需要进行具体的调整步骤, 我将更加细节的实现写入了我的Github, 传送门. 如果有错误, 欢迎指正, 我还在我的代码中添加了红黑树的输出的可视化, 可以看到树的结构是满足红黑树的性质的.

posted @   虾野百鹤  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 我干了两个月的大项目,开源了!
· 推荐一款非常好用的在线 SSH 管理工具
· 千万级的大表,如何做性能调优?
· 盘点!HelloGitHub 年度热门开源项目
· Phi小模型开发教程:用C#开发本地部署AI聊天工具,只需CPU,不需要GPU,3G内存就可以运行,
点击右上角即可分享
微信分享提示
CONTENTS