目录
前言:
在C++中,红黑树是二叉搜索树的另一种优化版本,他与AVL树的区别在于保持树的平衡方式不同,AVL树保持平衡的方式是在节点中多存储一个成员来记录平衡因子,红黑树保持平衡的方式也是增加了一个成员,但是该成员的作用是记录节点的两种状态(颜色):红色--黑色。当然只记录颜色并不能保持平衡,红黑树还规定最长路径的节点个数不会超过最短路径的节点个数的两倍,因此红黑树不会因为插入有序数据而演变成“单支树”。
红黑树有如下规则:
1、顾名思义,红黑树的节点只能有黑色和红色两种状态。
2、根结点默认为黑色。
3、红色节点的两个孩子只能是黑色节点。
4、插入的节点默认为红色节点。
5、每条路径的黑色节点都相同。
红黑树正确示意图:
红黑树错误示意图:
通过上述对红黑树的简述,可以给出红黑树的节点代码:
- #define _CRT_SECURE_NO_WARNINGS 1
-
- enum Colour//定义一个枚举
- {
- RED,
- BLACK,
- };
-
- template<class K, class V>
- struct RBTreeNode
- {
- RBTreeNode
* _left;//指向左孩子 - RBTreeNode
* _right;//指向右孩子 - RBTreeNode
* _parent;//指向父母节点 - pair
_kv;//记录数据 - Colour _col;//若是AVL树这里记载的是平衡因子,红黑树是颜色
-
- RBTreeNode(const pair
& kv) - :_left(nullptr)
- , _right(nullptr)
- , _parent(nullptr)
- , _kv(kv)
- , _col(RED)//默认插入的节点是红色
- {}
- };
可以发现,红黑树的节点代码几乎和AVL树一模一样,只是控制平衡的条件有区别,仅此而已。
红黑树的插入函数可以分两个步骤:
1、找到合适的位置插入,即二叉搜索树插入的逻辑(小于根节点的放在左边,大于根节点的放在右边)。
2、因为插入的节点默认为红色,则插入节点后,查看当前树是否破坏了红黑树的规则,即观察其节点的父母节点是否为红色,如果是则需要进行调整操作(规则3)。
在分析之前,先确定好节点之间的关系名称(cur表示新插入的节点,parant表示父母节点,uncle表示叔叔节点,gparent表示祖父节点):
当叔叔节点存在且为红,父母节点为红,祖父节点为黑:
最后可以发现,经过一系列的调整后符号红黑树的规则。
情况二又分两种情况:1、叔叔节点为黑色。2、不存在叔叔节点。
1、其他条件和情况一相同,但是叔叔节点是黑色的:
从上图可以发现,情况二多了旋转的步骤,并且在旋转之后将parent变黑,gparent变红,最终结果满足红黑树的规则。
2、若不存在叔叔节点:
综上所述,情况二可以总结为:旋转+变色(parent变黑,gparent变红)。
情况三即以上情况的插入点不一样,以上情况的插入节点都是插入在“边缘处”,通俗来讲就是左子树插入的节点都是作为左孩子插入的,而右子树插入的节点都是作为右孩子插入的,但是实际中总会出现在右子树中插入的节点是以左孩子的形式插入的(如下图),拿上述情况二的第二种情况举例,若插入的cur在parent的左边,那么以上的处理方法显然不能解决问题,具体操作图如下:
红黑树的实现代码如下:
- #define _CRT_SECURE_NO_WARNINGS 1
-
- #include
- using namespace std;
-
- enum Colour//定义一个枚举
- {
- RED,
- BLACK,
- };
-
- template<class K, class V>
- struct RBTreeNode
- {
- RBTreeNode
* _left;//指向左孩子 - RBTreeNode
* _right;//指向右孩子 - RBTreeNode
* _parent;//指向父母节点 - pair
_kv;//记录数据 - Colour _col;//若是AVL树这里记载的是平衡因子,红黑树是颜色
-
- RBTreeNode(const pair
& kv) - :_left(nullptr)
- , _right(nullptr)
- , _parent(nullptr)
- , _kv(kv)
- , _col(RED)//默认插入的节点是红色
- {}
- };
-
- template<class K, class V>
- class RBTree//红黑树类
- {
- typedef RBTreeNode
Node; - public:
- ~RBTree()
- {
- _Destroy(_root);
- _root = nullptr;
- }
- public:
- bool Insert(const pair
& kv) //插入函数 - {
- if (_root == nullptr)
- {
- _root = new Node(kv);
- _root->_col = BLACK;//第一次插入的根结点手动置黑
- return 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 false;
- }
- }
-
- cur = new Node(kv);
- if (parent->_kv.first > kv.first)
- {
- parent->_left = cur;
- }
- else
- {
- parent->_right = cur;
- }
- cur->_parent = parent;
-
- //当parent为红色则违法规则,需要调整
- while (parent && parent->_col == RED)
- {
- Node* grandfather = parent->_parent;
- if (grandfather->_left == parent)//父母在祖父的左边
- {
- Node* uncle = grandfather->_right;
- // 情况1:叔叔节点存在且为红,叔叔和父母进行变色处理,并继续往上处理
- if (uncle && uncle->_col == RED)
- {
- parent->_col = BLACK;
- uncle->_col = BLACK;
- grandfather->_col = RED;
-
- // 继续往上调整
- cur = grandfather;
- parent = cur->_parent;
- }
- else // 情况2:叔叔不存在/叔叔存在且为黑,旋转+变色
- {
- //父母在祖父的左边,而cur在父母的左边,说明是“边缘”情况
- if (cur == parent->_left)
- {
- RotateR(grandfather);
- parent->_col = BLACK;
- grandfather->_col = RED;
- }
- else//对应所述的情况三,需要旋转两次,并且cur变成了根结点
- {
- RotateL(parent);
- RotateR(grandfather);
- cur->_col = BLACK;
- grandfather->_col = RED;
- }
- break;
- }
- }
- else // 父母在祖父的右边
- {
- Node* uncle = grandfather->_left;
- // 情况1:u存在且为红,变色处理,并继续往上处理
- if (uncle && uncle->_col == RED)
- {
- parent->_col = BLACK;
- uncle->_col = BLACK;
- grandfather->_col = RED;
-
- // 继续往上调整
- cur = grandfather;
- parent = cur->_parent;
- }
- else // 情况2:叔叔不存在/叔叔存在且为黑,旋转+变色
- {
- //以下逻辑同上思路,只不过逻辑相反
- if (cur == parent->_right)
- {
- RotateL(grandfather);
- grandfather->_col = RED;
- parent->_col = BLACK;
- }
- else
- {
- RotateR(parent);
- RotateL(grandfather);
- cur->_col = BLACK;
- grandfather->_col = RED;
- }
-
- break;
- }
- }
- }
- _root->_col = BLACK;//根结点始终为黑
-
- return true;
- }
-
- void InOrder()
- {
- _InOrder(_root);
- }
-
- bool IsRedB()//检查红黑树
- {
- if (_root && _root->_col == RED)
- {
- cout << "根节点颜色是红色" << endl;
- return false;
- }
-
- int benchmark = 0;
- Node* cur = _root;
- while (cur)
- {
- if (cur->_col == BLACK)
- ++benchmark;
- cur = cur->_left;
- }
-
- // 连续红色节点
- return _Check(_root, 0, benchmark);
- }
-
- int Height()
- {
- return _Height(_root);
- }
-
- private:
- void _Destroy(Node* root)//释放空间
- {
- if (root == nullptr)
- {
- return;
- }
-
- _Destroy(root->_left);
- _Destroy(root->_right);
- delete root;
- }
-
- int _Height(Node* root)
- {
- if (root == NULL)
- return 0;
-
- int leftH = _Height(root->_left);
- int rightH = _Height(root->_right);
-
- return leftH > rightH ? leftH + 1 : rightH + 1;
- }
-
- bool _Check(Node* root, int blackNum, int benchmark)//红黑树的验证
- {
- if (root == nullptr)//走到空,就判断黑色节点数量是否一样
- {
- if (benchmark != blackNum)
- {
- cout << "某条路径黑色节点的数量不相等" << endl;
- return false;
- }
-
- return true;
- }
-
- if (root->_col == BLACK)//重新记录每条路径下的黑色节点
- {
- ++blackNum;
- }
-
- if (root->_col == RED
- && root->_parent
- && root->_parent->_col == RED)//连续红色节点也会报错
- {
- cout << "存在连续的红色节点" << endl;
- return false;
- }
-
- //每次递归都把黑色节点数量传给下一个栈帧
- return _Check(root->_left, blackNum, benchmark)
- && _Check(root->_right, blackNum, benchmark);
- }
-
- void _InOrder(Node* root)//中序遍历打印
- {
- if (root == nullptr)
- {
- return;
- }
-
- _InOrder(root->_left);
- cout << root->_kv.first << " ";
- _InOrder(root->_right);
- }
-
- void RotateL(Node* parent)//左旋
- {
- Node* subR = parent->_right;
- Node* subRL = subR->_left;
-
- parent->_right = subRL;
- if (subRL)
- subRL->_parent = parent;
-
- Node* ppnode = parent->_parent;
-
- subR->_left = parent;
- parent->_parent = subR;
-
- if (ppnode == nullptr)
- {
- _root = subR;
- _root->_parent = nullptr;
- }
- else
- {
- if (ppnode->_left == parent)
- {
- ppnode->_left = subR;
- }
- else
- {
- ppnode->_right = subR;
- }
-
- subR->_parent = ppnode;
- }
- }
-
- void RotateR(Node* parent)//右旋
- {
- Node* subL = parent->_left;
- Node* subLR = subL->_right;
-
- parent->_left = subLR;
- if (subLR)
- subLR->_parent = parent;
-
- Node* ppnode = parent->_parent;
-
- subL->_right = parent;
- parent->_parent = subL;
-
- if (parent == _root)
- {
- _root = subL;
- _root->_parent = nullptr;
- }
- else
- {
- if (ppnode->_left == parent)
- {
- ppnode->_left = subL;
- }
- else
- {
- ppnode->_right = subL;
- }
- subL->_parent = ppnode;
- }
- }
-
- private:
- Node* _root = nullptr;
- };
-
- int main()
- {
- int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
- RBTree<int, int> t1;
- for (auto e : a)
- {
- t1.Insert(make_pair(e, e));
- }
-
- t1.InOrder();//j检查打印出来的数据是否有序
- cout << endl << t1.IsRedB() << endl;
- return 0;
- }
运行结果:
从上面代码可以看出,检验一棵树是否为红黑树,从以下几个方面进行判断:函数IsRedB的作用是判断根结点是否为黑色,然后随便遍历一条路径,记录该路径下黑色节点的数量(规则5)。
接着把记录下来的黑色节点数量传给函数_check,并且让_check函数把所有路径下的黑色节点记录下来,一一的去跟之前记录好的数据进行对比,若有不相等的情况说明该树不是红黑树,另外_check函数内还进行了红色节点是否连续的判断(规则3)。
以上就是关于红黑树的讲解,红黑树的重点还是在于了解红黑树的调整逻辑,理清叔叔节点和父母节点他们的位置关系,最核心的还是叔叔节点,他的存在与否,是红色还是黑色都影响最终的调整规律。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!