目录
1.红黑树的介绍
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
红黑树是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目--------来自百度百科
2.红黑树的基本性质和节点的定义
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)从性质5中我们可以推断出:如果一个结点存在黑子结点,那么该结点肯定有两个子结点
下面来解释一下图片中的问题:
从性质三我们可以得出在红黑树中不存在连续的红色节点在根据性质四我们可以得出所有路径中黑色节点的个数是相同的。
我们假设从根节点到叶子节点的个数为N,那么最短路径全都是黑色节点构成即长度为N.
最长路径则是由红色和黑色交替构成,在该路径中红色节点和黑树节点的个数相同,在根据性质4我们就可以得到最长路径的长度为2N,所以在黑树中最长路径不会超过最短路径的2倍,从而达到了近似平衡。
下面我们来判断一颗树是否是红黑树:
显然这颗树不是红黑树从根节点55到38这个节点,而从38有两条路径一条是到空一条是到25显然这两条路径中的黑色节点数不相同,一条是3,一条是2.所以这颗树不是红黑树
2.2
如果我们要插入一个节点我们是希望他是红色节点还是黑树节点?很显然是红色节点,如果插入的是黑色节点,这会破坏红黑树中任何一条路径中黑色节点的数量相同。对应这种情况我们对红黑树进行调整就变得很麻烦。因此我们希望插入的节点为红色节点。
对应节点代码:
enum Colour { RED, BLACK, }; template<class K,class V> struct RBTreeNode { RBTreeNode(const pair<K,V>&kv) :_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_kv(kv) ,_col(RED) {} RBTreeNode<K, V>* _left;//左指针 RBTreeNode<K, V>* _right; RBTreeNode<K, V>* _parent;//父亲指针 pair<K, V>_kv; Colour _col;//每个节点都要有颜色 };
3.红黑树的旋转
1.左旋:
首先断开节点PL与右子节点G的关系,同时将其右子节点的引用指向节点C2;然后断开节点G与左子节点C2的关系,同时将G的左子节点的应用指向节点PL。
2.右旋
首先断开节点G与左子节点PL的关系,同时将其左子节点的引用指向节点C2;然后断开节点PL与右子节点C2的关系,同时将PL的右子节点的应用指向节点G。
对应
void RotateR(Node* parent) { Node* cur = parent->_left; Node* curR = cur->_right;//cur的右子树 Node* pparent = parent->_parent;//保存parent的父亲节点 //将cur右子树链接到parent的左侧 parent->_left = curR; if (curR) curR->_parent = parent; //将parent连接到cur的右侧 cur->_right = parent; parent->_parent = cur; //将cur与pparent链接起来 if (pparent == nullptr)//cur变成新的根 { _root = cur; cur->_parent = nullptr; } else//pparent不为根 { cur->_parent = pparent; if (parent == pparent->_left)//parent在父亲节点的左侧 { pparent->_left = cur; } else { pparent->_right = cur; } } } //左单旋 void RotateL(Node* parent)//左旋 { Node* cur = parent->_right;//右变高,不可能为空 Node* curL = cur->_left; Node* pprent = parent->_parent; //curL连接到parent上 parent->_right = curL; if (curL) curL->_parent = parent; //parent连接到cur上 cur->_left = parent; parent->_parent = cur; //cur链接到pprent上 if (pprent == nullptr)//根 { _root = cur; cur->_parent = nullptr; } else//不为根 { cur->_parent = pprent; //判断链接在哪一侧 if (pprent->_left == parent) { pprent->_left = cur; } else { pprent->_right = cur; } } }
4.红黑树的插入
我们先来看一张图片了解一下一些名词
红黑树的插入和搜索二叉树的思路基本一样只不过比搜索二叉树多了调节操作。
1.根据大小找到插入的位置
2.创建对应节点并与父亲链接
3.调整红黑树
插入情况一
插入的为空树直接构建节点并返回。
插入情况二
插入的节点的父亲节点为黑色节点:插入66这个节点
我们发现他的父亲是黑色节点,此时并没有违反黑红树的基本性质,插入结束。
插入情况三
插入之后需要变色不需要旋转,也就是插入节点的父亲为红色并且叔叔节点为红色。
插入51这节点
我们发现他的父亲节点是红色的并且叔叔节点为红色此时我们只需要将父亲节点和叔叔节点变黑将祖父节点变红,并从祖父节点往上查看是否违法红黑树的规则。
最后结束之后整棵树是这样的:
插入情况四
插入节点父亲节点为红色但是其叔叔节点为黑色或者不存在。
此时的插入又分为有4种情况:
情况1:插入节点是父亲的左孩子并且父亲是祖父的左孩子
1.插入65节点
此时父亲节点和插入节点形成了两个连续的红色节点,并且同时在左侧(LL型)需要对祖父节点进行右单旋,并变色。变色:将父亲节点变成黑色,祖父节点变成红色
情况2:插入节点为父亲的右孩子并且父亲是祖父的左孩子。
插入节点67:
此时父亲节点和插入节点形成了两个连续的红色节点,并且一个在左边一个在右边是一条折线(LR)需要进行双旋加变色处理。先对父亲节点进行一个左单旋在对祖父节点进行一个右单旋。并将父亲节点的颜色设置为黑色,祖父节点设置为红色。
情况3:
父亲节点为祖父节点的右孩子并且插入节点为父亲节点的右孩子
插入节点70:
此时插入节点和父亲节点形成了二个红色节点同时在右边RR型,我们只要对其进行一个左单旋加变色处理。将父亲的颜色变为黑色,将祖父节点颜色变成红色。
情况四:
父亲节是祖父节点的右孩子并且插入节点是父亲的左孩子。
插入68这个节点:
父亲节点和插入节点形成了两个连续的红色节点,属于RL型 。此时我们只需要对父亲进行一个右单旋在对祖父进行一个左单选,在变色。
变色:将父亲节点变成黑色,将祖父节点变成红色。
红黑树插入总结:
无需调整 | 变色即可 | 旋转+变色 |
插入节点的父亲节点是黑色 | 插入节点的父亲是红色并且叔叔节点是红色 | 父节点为红色左节点,叔父节点为黑色,插入左子节点(左旋) |
无 | 无 | 父节点为红色左节点,叔父节点为黑色,插入右子节点(左右双旋) |
无 | 无 | 父节点为红色右节点,叔父节点为黑色,插入右子节点(左旋) |
无 | 无 | 父节点为红色右节点,叔父节点为黑色,插入左子节点(右左双旋) |
对应插入代码:
pair<Node* ,bool>Insert(const pair<K, V>& kv) { if (!_root) {//情况1空树 _root = new Node(kv); _root->_col = BLACK; return make_pair(_root, true); } Node* parent = nullptr; Node* cur = _root;//通过值的大小来找到对应位置 while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else { return make_pair(cur, false); } } Node*newnode = new Node(kv); newnode->_col = RED; if (parent->_kv.first < kv.first) {//和父亲节点进行链接 parent->_right= newnode; newnode->_parent = parent; } else { parent->_left = newnode; newnode->_parent = parent; } cur = newnode; while (parent&&parent->_col==RED)//父亲存在且父亲是红色需要处理 { //关键是看叔叔 Node* grandfather = parent->_parent; if (parent == grandfather->_left) {// Node* uncle = grandfather->_right; //uncle存在且为红, //把祖父变红,祖父的两个孩子都变黑 if (uncle && uncle->_col == RED) {//情况三 parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = grandfather->_parent; } else {//叔叔不存在,或者叔叔不存在 if (cur == parent->_left) {//情况四中的情况1 RotateR(grandfather); grandfather->_col = RED; parent->_col = BLACK; } else {//情况四中的情况2 RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } else {//是祖父的右 Node* uncle = grandfather->_left;//情况三 if (uncle && uncle->_col == RED) { uncle->_col = BLACK; parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = grandfather->_parent; } else { if (cur == parent->_right) {//情况四中的情况三; RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else {//情况四中的小情况四 RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK;//无论如何根节点都是黑色的 return make_pair(newnode,true); }
5.红黑树的删除
红黑树的删除相比插入要复杂的多
大致步骤如下:
1.根据二叉搜索树的性质查找到该节点
2.寻找节点替代该节点
3.调整红黑树让他平衡
我们首先来回顾一下二叉搜索树的删除:
情况1:若删除结点无子结点,直接删除
情况2:若删除结点只有一个子结点,用子结点替换删除结点
情况3:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点
而情况3又可以转换为情况1进行处理。
下面我们来看红黑树的删除
情况1:
要删除的节点为红色的叶子节点:删除69这个红色叶子节点
直接删除即可
情况2
删除的黑色叶子节点,父亲节点为红色,兄弟节点为黑色并且没有一个红色的孩子节点
我们要删的是64这个节点但是我们可以转换为删除63这个叶子节点,此时我们只需要变色即可将父亲节点变为黑色兄弟节点变为红色即可。
情况三:删除的是删除的是黑色叶子节点并且兄弟节点为黑色并且有一个红色孩子节点
我们要删除88这个黑色叶子节点。删除之后就不平衡了我们发现他的兄弟节点为黑色并且有一个红色孩子节点,于是我们就向兄弟节点借,怎么借了,我们发现兄弟节点和他的红色叶子节点在一条直线上,所以我们只要对80进行一个右单旋加变色处理即可。
变色:旋转后的中心节点继承原来父亲节点颜色,旋转后的左右节点均染成黑树
情况三中还有一个小情况:兄弟节点和红色孩子节点不在一条直线上:即在兄弟节点的右边
同样的删除88这个节点这种情况和上面那种情况不同的是兄弟节点的红色孩子节点不在一定直线上,此时我们需要进行双旋处理即左右双旋+变色处理,即先对兄弟节点进行一个左单旋在对父亲节点进行一个右单旋,在进行变色处理,变色只需要将中心节点继承原来的父亲节点的颜色,旋转之后的左右节点均变为黑色。
情况三种还有就是被删除节点是父亲的左孩子同样也是上面这样的做法,只不过旋转方式不同而已。老铁们可以自己下去画一画,如果实在需要请在评论区留言。
情况4
删除黑色叶子节点,兄弟节点为黑树,有两个红色节点。
这种情况又分为两种情况:被删除节点是父亲的左孩子和被删除节点是父亲的右孩子,在这里就只举一个例子:
在这里我们要删除88这个节点,此时他的兄弟节点只有 2个黑树节点,此时我们可以进行双旋也可进行单旋,在这里我旋转单旋也就是右单旋。
对父亲节点进右单旋,旋转后的中心节点继承父亲节点的颜色,旋转后左右节点均变为黑色。
情况5:
删除的节点为黑树色节点并且有一个红色子节点。
这种情况非常的简单,用红色的子节点来代替删除节点,并将红色子节点变成黑色。
删除80这个节点:
情况6:
删除节点是叶子节点,但是兄弟节点为红色节点:
删除88这个节点:
此时他的兄弟节点为红色,在这里我们88真正的黑兄弟节点其实是76,这里我们强行让88和76做兄弟,只需要对父亲节点进行一个右单旋,在调用套用之前情况写的代码:
单然还有左单选的情况就是被删除节点左父亲的左边,就是旋转方向不一样而已。老铁们可以自己下去画一画.
情况7:
删除的黑色叶子节点,父亲节点为黑色,兄弟节点为黑色并且没有一个红色的孩子节点:
删除88这个节点
首先将兄弟节点变红在调用上面的情况的代码,注意实际情况下80节点上面可能还有节点。
对应代码:
//删除函数 bool Erase(const K& key) { //用于遍历二叉树 Node* parent = nullptr; Node* cur = _root; //用于标记实际的待删除结点及其父结点 Node* delParentPos = nullptr; Node* delPos = nullptr; while (cur) { if (key < cur->_kv.first) //所给key值小于当前结点的key值 { //往该结点的左子树走 parent = cur; cur = cur->_left; } else if (key > cur->_kv.first) //所给key值大于当前结点的key值 { //往该结点的右子树走 parent = cur; cur = cur->_right; } else //找到了待删除结点 { if (cur->_left == nullptr) //待删除结点的左子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_right; //让根结点的右子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } break; //进行红黑树的调整以及结点的实际删除 } else if (cur->_right == nullptr) //待删除结点的右子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_left; //让根结点的左子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } break; //进行红黑树的调整以及结点的实际删除 } else //待删除结点的左右子树均不为空 { //替换法删除 //寻找待删除结点右子树当中key值最小的结点作为实际删除结点 Node* minParent = cur; Node* minRight = cur->_right; while (minRight->_left) { minParent = minRight; minRight = minRight->_left; } cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的key cur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的value delParentPos = minParent; //标记实际删除结点的父结点 delPos = minRight; //标记实际删除的结点 break; //进行红黑树的调整以及结点的实际删除 } } } if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点 { return false; } //记录待删除结点及其父结点(用于后续实际删除) Node* del = delPos; Node* delP = delParentPos; //调整红黑树 if (delPos->_col == BLACK) //删除的是黑色结点 { if (delPos->_left) //待删除结点有一个红色的左孩子(不可能是黑色) { delPos->_left->_col = BLACK; //将这个红色的左孩子变黑即可 } else if (delPos->_right) //待删除结点有一个红色的右孩子(不可能是黑色) { delPos->_right->_col = BLACK; //将这个红色的右孩子变黑即可 } else //待删除结点的左右均为空 { while (delPos != _root) //可能一直调整到根结点 { if (delPos == delParentPos->_left) //待删除结点是其父结点的左孩子 { Node* brother = delParentPos->_right; //兄弟结点是其父结点的右孩子 //:brother为红色 if (brother->_col == RED) { delParentPos->_col = RED; brother->_col = BLACK; RotateL(delParentPos); //需要继续处理 brother = delParentPos->_right; //更新brother(否则在本循环中执行其他情况的代码会出错) } //:brother为黑色,且其左右孩子都是黑色结点或为空 if (((brother->_left == nullptr) || (brother->_left->_col == BLACK)) && ((brother->_right == nullptr) || (brother->_right->_col == BLACK))) { brother->_col = RED; if (delParentPos->_col == RED) { delParentPos->_col = BLACK; break; } //需要继续处理 delPos = delParentPos; delParentPos = delPos->_parent; } else { //:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空 if ((brother->_right == nullptr) || (brother->_right->_col == BLACK)) { /* brother->_left->_col = BLACK; brother->_col = RED;*/ RotateR(brother); //需要继续处理 brother = delParentPos->_right; //更新brother(否则执行下面情况四的代码会出错) } //:brother为黑色,且其右孩子是红色结点 brother->_col = delParentPos->_col; delParentPos->_col = BLACK; brother->_right->_col = BLACK; RotateL(delParentPos); break; //情况四执行完毕后调整一定结束 } } else //delPos == delParentPos->_right //待删除结点是其父结点的左孩子 { Node* brother = delParentPos->_left; //兄弟结点是其父结点的左孩子 //:brother为红色 if (brother->_col == RED) //brother为红色 { delParentPos->_col = RED; brother->_col = BLACK; RotateR(delParentPos); //需要继续处理 brother = delParentPos->_left; //更新brother(否则在本循环中执行其他情况的代码会出错) } //:brother为黑色,且其左右孩子都是黑色结点或为空 if (((brother->_left == nullptr) || (brother->_left->_col == BLACK)) && ((brother->_right == nullptr) || (brother->_right->_col == BLACK))) { brother->_col = RED; if (delParentPos->_col == RED) { delParentPos->_col = BLACK; break; } //需要继续处理 delPos = delParentPos; delParentPos = delPos->_parent; } else { //:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空 if ((brother->_left == nullptr) || (brother->_left->_col == BLACK)) { /*brother->_right->_col = BLACK; brother->_col = RED;*/ RotateL(brother); //需要继续处理 brother = delParentPos->_left; //更新brother(否则执行下面情况四的代码会出错) } //:brother为黑色,且其左孩子是红色结点 brother->_col = delParentPos->_col; delParentPos->_col = BLACK; brother->_left->_col = BLACK; RotateR(delParentPos); break; //情况四执行完毕后调整一定结束 } } } } } //进行实际删除 if (del->_left == nullptr) //实际删除结点的左子树为空 { if (del == delP->_left) //实际删除结点是其父结点的左孩子 { delP->_left = del->_right; if (del->_right) del->_right->_parent = delP; } else //实际删除结点是其父结点的右孩子 { delP->_right = del->_right; if (del->_right) del->_right->_parent = delP; } } else //实际删除结点的右子树为空 { if (del == delP->_left) //实际删除结点是其父结点的左孩子 { delP->_left = del->_left; if (del->_left) del->_left->_parent = delP; } else //实际删除结点是其父结点的右孩子 { delP->_right = del->_left; if (del->_left) del->_left->_parent = delP; } } delete del; //实际删除结点 return true; }
6.红黑树总结
综上,红黑树删除后自平衡的处理可以总结为:
- 自己能搞定的自消化
- 自己不能搞定的叫兄弟帮忙
- 兄弟都帮忙不了的,通过父母,找远方亲戚。
哈哈,是不是跟现实中很像,当我们有困难时,首先先自己解决,自己无力了总兄弟姐妹帮忙,如果连兄弟姐妹都帮不上,再去找远方的亲戚了。这里记忆应该会好记点~
7.红黑树与AVL树总结
最后我们如何验证红黑树呢?
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)回到红黑树的规则我们只需要验证前四点即可。难点在于验证性质4,我们可以先统计一个条路径黑色节点的数量,在变量红色黑树的其他路径看黑色节点数是否相等
对应代码:
bool CheckBlance() { if (!_root) { return true; } if (_root->_col == RED) { cout << "根节点为红色" << endl; } int blacknum = 0; Node* left = _root; while (left) { if (left->_col == BLACK) { blacknum++; } left = left->_left; } int Count = 0; return _CheckBlance(_root, blacknum, Count); } bool _CheckBlance(Node* root, int blackNum, int Count) { if (!root) { if (Count != blackNum) { cout << "黑色节点的数量不相等" << endl; return false; } return true; } if (root->_col == RED && root->_parent->_col == RED) {//连续的红色节点 return false; } if (root->_col == BLACK) { Count++; } return _CheckBlance(root->_left, blackNum, Count) && _CheckBlance(root->_right, blackNum, Count); }
代码汇总:
#pragma once #include<iostream> using namespace std; enum Colour { RED, BLACK, }; template<class K,class V> struct RBTreeNode { RBTreeNode(const pair<K,V>&kv) :_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_kv(kv) ,_col(RED) {} RBTreeNode<K, V>* _left; RBTreeNode<K, V>* _right; RBTreeNode<K, V>* _parent; pair<K, V>_kv; Colour _col; }; template<class K,class V> class RBTree { typedef RBTreeNode<K, V> Node; public: pair<Node* ,bool>Insert(const pair<K, V>& kv) { if (!_root) { _root = new Node(kv); _root->_col = BLACK; return make_pair(_root, true); } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else { return make_pair(cur, false); } } Node*newnode = new Node(kv); newnode->_col = RED; if (parent->_kv.first < kv.first) { parent->_right= newnode; newnode->_parent = parent; } else { parent->_left = newnode; newnode->_parent = parent; } cur = newnode; while (parent&&parent->_col==RED)//父亲存在且父亲是红色需要处理 { //关键是看叔叔 Node* grandfather = parent->_parent; if (parent == grandfather->_left) { Node* uncle = grandfather->_right; //uncle存在且为红, //把祖父变红,祖父的两个孩子都变黑 if (uncle && uncle->_col == RED) { parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = grandfather->_parent; } else {//叔叔不存在,或者叔叔不存在 if (cur == parent->_left) {//单旋 RotateR(grandfather); grandfather->_col = RED; parent->_col = BLACK; } else { RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } else {//是祖父的右 Node* uncle = grandfather->_left; if (uncle && uncle->_col == RED) { uncle->_col = BLACK; parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = grandfather->_parent; } else { if (cur == parent->_right) { RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK; return make_pair(newnode,true); } void RotateR(Node* parent) { Node* cur = parent->_left; Node* curR = cur->_right;//cur的右子树 Node* pparent = parent->_parent;//保存parent的父亲节点 //将cur右子树链接到parent的左侧 parent->_left = curR; if (curR) curR->_parent = parent; //将parent连接到cur的右侧 cur->_right = parent; parent->_parent = cur; //将cur与pparent链接起来 if (pparent == nullptr)//cur变成新的根 { _root = cur; cur->_parent = nullptr; } else//pparent不为根 { cur->_parent = pparent; if (parent == pparent->_left)//parent在父亲节点的左侧 { pparent->_left = cur; } else { pparent->_right = cur; } } } //左单旋 void RotateL(Node* parent)//左旋 { Node* cur = parent->_right;//右变高,不可能为空 Node* curL = cur->_left; Node* pprent = parent->_parent; //curL连接到parent上 parent->_right = curL; if (curL) curL->_parent = parent; //parent连接到cur上 cur->_left = parent; parent->_parent = cur; //cur链接到pprent上 if (pprent == nullptr)//根 { _root = cur; cur->_parent = nullptr; } else//不为根 { cur->_parent = pprent; //判断链接在哪一侧 if (pprent->_left == parent) { pprent->_left = cur; } else { pprent->_right = cur; } } } bool CheckBlance() { if (!_root) { return true; } if (_root->_col == RED) { cout << "根节点为红色" << endl; } int blacknum = 0; Node* left = _root; while (left) { if (left->_col == BLACK) { blacknum++; } left = left->_left; } int Count = 0; return _CheckBlance(_root, blacknum, Count); } bool _CheckBlance(Node* root, int blackNum, int Count) { if (!root) { if (Count != blackNum) { cout << "黑色节点的数量不相等" << endl; return false; } return true; } if (root->_col == RED && root->_parent->_col == RED) {//连续的红色节点 return false; } if (root->_col == BLACK) { Count++; } return _CheckBlance(root->_left, blackNum, Count) && _CheckBlance(root->_right, blackNum, Count); } //删除函数 bool Erase(const K& key) { //用于遍历二叉树 Node* parent = nullptr; Node* cur = _root; //用于标记实际的待删除结点及其父结点 Node* delParentPos = nullptr; Node* delPos = nullptr; while (cur) { if (key < cur->_kv.first) //所给key值小于当前结点的key值 { //往该结点的左子树走 parent = cur; cur = cur->_left; } else if (key > cur->_kv.first) //所给key值大于当前结点的key值 { //往该结点的右子树走 parent = cur; cur = cur->_right; } else //找到了待删除结点 { if (cur->_left == nullptr) //待删除结点的左子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_right; //让根结点的右子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } break; //进行红黑树的调整以及结点的实际删除 } else if (cur->_right == nullptr) //待删除结点的右子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_left; //让根结点的左子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } break; //进行红黑树的调整以及结点的实际删除 } else //待删除结点的左右子树均不为空 { //替换法删除 //寻找待删除结点右子树当中key值最小的结点作为实际删除结点 Node* minParent = cur; Node* minRight = cur->_right; while (minRight->_left) { minParent = minRight; minRight = minRight->_left; } cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的key cur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的value delParentPos = minParent; //标记实际删除结点的父结点 delPos = minRight; //标记实际删除的结点 break; //进行红黑树的调整以及结点的实际删除 } } } if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点 { return false; } //记录待删除结点及其父结点(用于后续实际删除) Node* del = delPos; Node* delP = delParentPos; //调整红黑树 if (delPos->_col == BLACK) //删除的是黑色结点 { if (delPos->_left) //待删除结点有一个红色的左孩子(不可能是黑色) { delPos->_left->_col = BLACK; //将这个红色的左孩子变黑即可 } else if (delPos->_right) //待删除结点有一个红色的右孩子(不可能是黑色) { delPos->_right->_col = BLACK; //将这个红色的右孩子变黑即可 } else //待删除结点的左右均为空 { while (delPos != _root) //可能一直调整到根结点 { if (delPos == delParentPos->_left) //待删除结点是其父结点的左孩子 { Node* brother = delParentPos->_right; //兄弟结点是其父结点的右孩子 //:brother为红色 if (brother->_col == RED) { delParentPos->_col = RED; brother->_col = BLACK; RotateL(delParentPos); //需要继续处理 brother = delParentPos->_right; //更新brother(否则在本循环中执行其他情况的代码会出错) } //:brother为黑色,且其左右孩子都是黑色结点或为空 if (((brother->_left == nullptr) || (brother->_left->_col == BLACK)) && ((brother->_right == nullptr) || (brother->_right->_col == BLACK))) { brother->_col = RED; if (delParentPos->_col == RED) { delParentPos->_col = BLACK; break; } //需要继续处理 delPos = delParentPos; delParentPos = delPos->_parent; } else { //:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空 if ((brother->_right == nullptr) || (brother->_right->_col == BLACK)) { /* brother->_left->_col = BLACK; brother->_col = RED;*/ RotateR(brother); //需要继续处理 brother = delParentPos->_right; //更新brother(否则执行下面情况四的代码会出错) } //:brother为黑色,且其右孩子是红色结点 brother->_col = delParentPos->_col; delParentPos->_col = BLACK; brother->_right->_col = BLACK; RotateL(delParentPos); break; //情况四执行完毕后调整一定结束 } } else //delPos == delParentPos->_right //待删除结点是其父结点的左孩子 { Node* brother = delParentPos->_left; //兄弟结点是其父结点的左孩子 //:brother为红色 if (brother->_col == RED) //brother为红色 { delParentPos->_col = RED; brother->_col = BLACK; RotateR(delParentPos); //需要继续处理 brother = delParentPos->_left; //更新brother(否则在本循环中执行其他情况的代码会出错) } //:brother为黑色,且其左右孩子都是黑色结点或为空 if (((brother->_left == nullptr) || (brother->_left->_col == BLACK)) && ((brother->_right == nullptr) || (brother->_right->_col == BLACK))) { brother->_col = RED; if (delParentPos->_col == RED) { delParentPos->_col = BLACK; break; } //需要继续处理 delPos = delParentPos; delParentPos = delPos->_parent; } else { //:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空 if ((brother->_left == nullptr) || (brother->_left->_col == BLACK)) { /*brother->_right->_col = BLACK; brother->_col = RED;*/ RotateL(brother); //需要继续处理 brother = delParentPos->_left; //更新brother(否则执行下面情况四的代码会出错) } //:brother为黑色,且其左孩子是红色结点 brother->_col = delParentPos->_col; delParentPos->_col = BLACK; brother->_left->_col = BLACK; RotateR(delParentPos); break; //情况四执行完毕后调整一定结束 } } } } } //进行实际删除 if (del->_left == nullptr) //实际删除结点的左子树为空 { if (del == delP->_left) //实际删除结点是其父结点的左孩子 { delP->_left = del->_right; if (del->_right) del->_right->_parent = delP; } else //实际删除结点是其父结点的右孩子 { delP->_right = del->_right; if (del->_right) del->_right->_parent = delP; } } else //实际删除结点的右子树为空 { if (del == delP->_left) //实际删除结点是其父结点的左孩子 { delP->_left = del->_left; if (del->_left) del->_left->_parent = delP; } else //实际删除结点是其父结点的右孩子 { delP->_right = del->_left; if (del->_left) del->_left->_parent = delP; } } delete del; //实际删除结点 return true; } private: Node* _root=NULL; };
参考资料: