前言
本文将会向你介绍红黑树的概念、性质,以及如何手撕红黑树
1 文章重点
文本首先引入红黑树的概念和性质,性质非常重要对于后面的插入操作来说,文章的核心放在了插入部分,另外看插入部分之前记得看声名和节点的定义哦~
2 引入红黑树
2.1概念
首先红黑树是一颗二叉搜索树,每个节点都有颜色,红色或黑色,最长路径最多是最短路径的二倍
2.2性质
1、 每个节点不是红色就是黑色。
2、 根部节点是黑色的。
3、 一条路径上不能出现连续的红色节点
4、每条路径都必须包含相同数量的黑色节点。
3 测试
// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptrNode* Find(const pair<K, V>& kv){Node* cur = _root;Node* parent = _root;Node* grandParent = parent->_parent;//判断是否为空树if (_root == nullptr){return nullptr;}//寻找插入位置else{Node* parent = cur;while (cur){//记录父节点的位置,便于后续的链接操作parent = cur;//向左遍历if (cur->_kv.first > kv.first){cur = cur->_left;}//向右遍历else if (cur->_kv.first < kv.first){cur = cur->_right;}//已有else return cur;}}}//中序遍历,验证是否为二叉搜索树void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}size_t _Height(Node* root){if (root == nullptr){return 0;}int leftHeight = _Height(root->_pLeft);int rightHeight = _Height(root->_pRight);return (leftHeight > rightHeight) ? leftHeight + 1 : rightHeight + 1;}int Height(){return _Height(_root);}bool Check(Node* root, int blackNum, const int ref){if (root == nullptr) //到根部看看当前路径黑色节点和标准值是否一致{//cout << blackNum << endl;if (blackNum != ref){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}// 检查子比较复杂,可以反过来去检查红节点父是否为黑色if (root->_cl == RED && root->_parent->_cl == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_cl == BLACK){++blackNum; //为黑节点加一}return Check(root->_left, blackNum, ref)&& Check(root->_right, blackNum, ref);}bool _IsBalance(Node* root){if (root == nullptr){return true;}if (root->_cl == RED){return false;}int ref = 0;Node* cur = root;//计算一条路径的黑色节点标准值while (cur){if (cur->_cl == BLACK){ref++;}cur = cur->_left;}int blackNum = 0;return Check(root, blackNum, ref); }bool IsBalance(){return _IsBalance(_root);}
Check 函数是一个递归函数,用于检查从当前节点到叶子节点的路径上的黑色节点个数是否与参考值 ref 相等。
参数 root 表示当前节点指针,blackNum 表示根节点到当前节点的路径上的黑色节点个数,ref 是参考值。
如果当前节点为空指针,即到达了叶子节点,那么检查路径上的黑色节点个数 blacknum 是否等于参考值 ref,如果不相等则返回false,否则返回 true。
如果当前节点的颜色为红色,并且父节点也是红色,表示存在连续的红节点,不符合红黑树的性质,返回 false。
如果当前节点的颜色为黑色,将 blackNum 值加一。 递归地调用 Check 函数检查左子节点和右子节点,并将当前节点的黑色节点个数blacknum 作为参数传递。
IsBalance 函数用于检查整个红黑树是否符合红黑树的性质。
如果红黑树为空树,即根节点为空,认为是一棵合法的红黑树,返回 true。
如果根节点的颜色是红色,违反了红黑树的性质,返回 false。
初始化 blacknum 为 0,用于记录每条路径的黑色节点个数。 初始化 ref 为 0,作为参考值。
通过遍历从根节点到最左子节点的路径,统计参考值 ref,即路径上的黑色节点个数。 调用 Check
函数,传入根节点、路径上的黑色节点个数 blacknum 和参考值 RefVal 进行检查
插入一千个数据(未全列出)
4 插入(重点)
声名
为了方便叙述,将进行以下定义
cur©——当前节点
parent§——父节点
grandParent(g)——父节点的父节点
uncle(u)——父节点的兄弟节点
节点的定义
enum Color
{RED,BLACK
};
template<class K, class V>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;RBTreeNode* _pHead;Color _cl;pair<K, V> _kv;
template<class K, class V>RBTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr),_pHead(nullptr), _cl(RED), _kv(kv){}
};
_left:指向左子节点的指针。
_right:指向右子节点的指针。
_parent:指向父节点的指针。
_kv:存储键值对的成员变量。这里使用了 pair<K, V> 类型来表示键值对,其中 K 是键的类型,V 是值的类型。
_cl:表示节点的颜色。这里使用枚举类型来表示,其中 RED 表示红色,BLACK 表示黑色,构造出来的节点默认为红色
插入
对于新插入节点,我们设置为红色,原因是红黑树每条路径都必须包含相同数量的黑色节点(性质4),新插入红节点不一定破坏红黑树的结构,新插入黑色节点一定不符合性质4而且很难调整(新增一条路径的黑色节点,其他路径也需要增加(会很麻烦))
bool Insert(const pair<K, V>& kv){ //记录当前节点Node* cur = _root;//记录父节点Node* parent = _root;//判断是否为空树if (_root == nullptr){//直接插入_root = new Node(kv);//插入成功_root->_cl = BLACK;return true;}//寻找插入位置else{ while (cur){//记录父节点的位置,便于后续的链接操作parent = cur;//向左遍历if (cur->_kv.first > kv.first){cur = cur->_left;}//向右遍历else if (cur->_kv.first < kv.first){cur = cur->_right;}//已有else return false;}cur = new Node(kv);//插入+链接if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//链接cur->_parent = parent;}//父亲为红,变色调整while (parent && parent->_cl == RED){//以下代码暂时省略......//1、parent为grandparent的左//情况一:uncle存在且为红,变色即可//无需讨论cur的位置,变色即可//情况二:uncle不存在或者为黑,旋转+变色//需讨论cur的位置//(1)cur为parent的左(进行右单旋)// g// p u//c//(2)cur为parent的右(进行左右双旋)// g// p u// c//2、parent为grandparent的右//情况一:uncle存在且为红,变色即可//无需讨论cur的位置,变色即可//情况二:uncle不存在或者为黑,旋转+变色//(1)cur为parent的右(进行左单旋)// g// u p// c//(2)cur为parent的右(进行右左双旋)// g// u p// c} //保持根部为黑色_root->_cl = BLACK;return true;}
注意:所有的情况解决方案都是根据规律总结出来的,由于将所有的情况列出来过于复杂,所以用抽象的子树来代替(三角形代表的就是抽象的子树),在这一情况中将会告诉你如果不用抽象子树来代替会有多复杂
(1)情况一:parent为grandparent的左
1.1uncle存在且为红,变色即可
Node* uncle = grandParent->_right;
if (uncle && uncle->_cl == RED)
{parent->_cl = uncle->_cl = BLACK;grandParent->_cl = RED;//继续向上调整cur = grandParent;parent = cur->_parent;
}
三角形表示抽象子树,下面会举例说明这里为什么会用抽象子树来表示
当c,d,e是没有黑色节点的子树,此时cur是新增,此时c,d,e子树处为一个红节点或者为空,a,b为空。我们只需要通过新插入节点以及p,u、g节点进行变色即可,如图将p,u变黑,将g变红即可
当c,d,e子树是包含一个黑色节点的子树,如图此时c(cur)便不是新插入的节点了,最下边的红色节点才是
这是c,d,e子树可能的情况,然而插入位置有4个,cde形状组合:444,合计组合为64*4 = 256种情况,如果c,d,e子树是包含2个黑色节点及以上的,那么情况是分析不完的
其实你会发现,只要满足某个规律(如下)
然后做出应有的调整
最后在向上调整就好了
以下情况(非必须)都会用抽象子树来模拟规律
1.2uncle不存在或者为黑,旋转+变色
1.2.1cur为parent的左(进行右单旋)
//(1)cur为parent的左
// g
// p u
//c
if (cur == parent->_left)
{RotateR(grandParent);grandParent->_cl = RED;parent->_cl = BLACK;
}
为了能让你更好的理解。此情况(uncle不存在或者为黑)的调整将会结合uncle存在且为红,变色即可的情况
如图:c、d、e是包含一个黑色节点的子树
规律:uncle存在且为红,变色即可并向上调整
我们将会发现此时满足u为黑
右旋
变色
注意c、d、e是包含一个黑色节点的子树
此时你便可以看看是否满足红黑树的性质了
1.2.2cur为parent的右(进行右左双旋)
//(1)cur为parent的右
// g
// p u
// c
else
{//左右双旋RotateL(parent);RotateR(grandParent);grandParent->_cl = RED;cur->_cl = BLACK;
}
(2)情况二:parent为grandparent的右
2.1uncle存在且为红,变色即可
Node* uncle = grandParent->_left;
if (uncle && uncle->_cl == RED)
{parent->_cl = uncle->_cl = BLACK;grandParent->_cl = RED;//继续向上调整cur = grandParent;parent = cur->_parent;
}
2.2uncle不存在或者为黑,旋转+变色
2.2.1cur为parent的右(进行左单旋)
//(1)cur为parent的右(左单旋)
// g
// u p
// c
if (cur == parent->_right)
{RotateL(grandParent);grandParent->_cl = RED;parent->_cl = BLACK;
}
2.2.2cur为parent的左(进行右左双旋)
//(1)cur为parent的右(右左双旋)
// g
// u p
// c
RotateR(parent);
RotateL(grandParent);
grandParent->_cl = RED;
cur->_cl = BLACK;
5 全部代码
enum Color
{RED,BLACK
};
template<class K, class V>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;RBTreeNode* _pHead;Color _cl;pair<K, V> _kv;
template<class K, class V>RBTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr),_pHead(nullptr), _cl(RED), _kv(kv){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://左单旋void RotateL(Node* pParent){Node* subR = pParent->_right;Node* subRL = subR->_left; //可能为空pParent->_right = subRL;subR->_left = pParent;Node* pPnode = pParent->_parent; //原来父亲的父亲pParent->_parent = subR;//可能为空if (subRL){subRL->_parent = pParent;}//链接:旋转整棵树if (_root == pParent){_root = subR;subR->_parent = nullptr;}//链接:旋转子树else{if (pPnode->_left == pParent){pPnode->_left = subR;}else if (pPnode->_right == pParent){pPnode->_right = subR;}subR->_parent = pPnode;}}// 右单旋void RotateR(Node* pParent){Node* subL = pParent->_left;Node* subLR = subL->_right;pParent->_left = subLR;//可能为空if (subLR){subLR->_parent = pParent;}Node* pPnode = pParent->_parent;subL->_right = pParent;pParent->_parent = subL;//旋转部分子树if (_root == pParent){ _root = subL;subL->_parent = nullptr;}//旋转整棵子树else{if (pPnode->_left == pParent){pPnode->_left = subL;}else{pPnode->_right = subL;}subL->_parent = pPnode;}}bool Insert(const pair<K, V>& kv){ //记录当前节点Node* cur = _root;//记录父节点Node* parent = _root;//判断是否为空树if (_root == nullptr){//直接插入_root = new Node(kv);//插入成功_root->_cl = BLACK;return true;}//寻找插入位置else{ while (cur){//记录父节点的位置,便于后续的链接操作parent = cur;//向左遍历if (cur->_kv.first > kv.first){cur = cur->_left;}//向右遍历else if (cur->_kv.first < kv.first){cur = cur->_right;}//已有else return false;}cur = new Node(kv);//插入+链接if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//链接cur->_parent = parent;}//父亲为红,变色调整while (parent && parent->_cl == RED){//记录原来节点的父亲的父节点Node* grandParent = parent->_parent;//情况一:uncle存在且为红,变色即可if (parent == grandParent->_left){Node* uncle = grandParent->_right;if (uncle && uncle->_cl == RED){parent->_cl = uncle->_cl = BLACK;grandParent->_cl = RED;//继续向上调整cur = grandParent;parent = cur->_parent;}//情况二:uncle不存在或者为黑,旋转+变色else{//(1)cur为parent的左// g// p u//cif (cur == parent->_left){RotateR(grandParent);grandParent->_cl = RED;parent->_cl = BLACK;}//(1)cur为parent的右// g// p u// celse{//左右双旋RotateL(parent);RotateR(grandParent);grandParent->_cl = RED;cur->_cl = BLACK;}break;}}//parent为grandparent的右else{//情况一:uncle存在且为红,变色即可Node* uncle = grandParent->_left;if (uncle && uncle->_cl == RED){parent->_cl = uncle->_cl = BLACK;grandParent->_cl = RED;//继续向上调整cur = grandParent;parent = cur->_parent;}//情况二:uncle不存在或者为黑,旋转+变色else{//(1)cur为parent的右(左单旋)// g// u p// cif (cur == parent->_right){RotateL(grandParent);grandParent->_cl = RED;parent->_cl = BLACK;}//(1)cur为parent的右(左单旋)else{//(1)cur为parent的右(右左双旋)// g// u p// cRotateR(parent);RotateL(grandParent);grandParent->_cl = RED;cur->_cl = BLACK;}break;}}} //保持根部为黑色_root->_cl = BLACK;return true;}
小结
今日的分享就到这里啦,如果本文存在疏漏或错误的的地方,还请您能够指出