目录
1.红黑树的概念
2.红黑树的性质
3.红黑树的节点定义
4.红黑树的插入操作
4.1按照二叉搜索数的规则插入一个新的节点
4.2根据红黑树的规则对该树进行调整
4.3插入的代码
5.红黑树的验证
6.红黑树与AVL树的比较
1.红黑树的概念
红黑树是一种二叉搜索树,在每一个节点上增加了一个存储位用来表示该节点的颜色,可以是红色也可以是黑色。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,故它是接近平衡
2.红黑树的性质
- 每个节点不是黑色就是红色
- 根节点是黑色
- 如果一个节点是红色,则它的两个孩子节点都是黑色(整棵树中父子节点之间不会出现连续的红色节点)
- 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,黑色节点的数量相同(每条路径上有相同数量的黑色节点)
- 每个叶子节点都是黑色的(这里的叶子节点指的是最后的空节点)
路径:从根节点开始,到该树的叶子节点(最后的空节点)结束

一棵红黑树中,最短的路径即所有节点都是黑色的,而最长的路径是红黑节点交替的,因此该树所有路径的长度在[h,2h]之间(最长和最短路径不一定存在)
3.红黑树的节点定义
enum Color
{Red,Black
};
template
struct RBTreeNode
{RBTreeNode(const T& val = T(),Color col = Red) //这里新插入的节点我们默认为红色:_parent(nullptr),_left(nullptr),_right(nullptr),_val(val),_col(col){ }RBTreeNode* _parent;RBTreeNode* _left;RBTreeNode* _right;T _val;Color _col;
};
新插入的节点我们默认为红色的原因:如果新插入的节点是黑色,就一定会破坏红黑树的第4条性质,这就影响到了整棵树;但是如果新插入的节点是红色,有可能会破坏红黑树的第3条性质,这仅仅影响到这一条路径
4.红黑树的插入操作
4.1按照二叉搜索数的规则插入一个新的节点
if (_root == nullptr)
{_root = new Node(val);_root->_col = Black;return true;
}
else
{Node* parent = nullptr;Node* cur = _root;while (cur){parent = cur;if (val > cur->_val){cur = cur->_right;}else if (val < cur->_val){cur = cur->_left;}else{return false;}}cur = new Node(val);if (cur->_val > parent->_val){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;
}
4.2根据红黑树的规则对该树进行调整
首先我们需要去判断新插入的节点有没有破坏红黑树的性质。首先去看新插入节点的父节点,如果父节点是黑色,则说明没有违反红黑树的任何性质,不需要进行调整;如果其父节点是红色,就破坏了红黑树的第3条性质,需要对该树进行调整,需要分情况讨论:
cur为新插入的节点,p是其父节点,g是p的父节点,u是p的兄弟节点
情况1:cur为红,p为红,g为黑,u存在且为红

这种情况我们只需要将g的颜色变为红色,p和u的颜色变为红色即可
注:如果g为根节点,需要将g的颜色再变为黑色;如果g不是根节点,这仅仅是一棵子树,需要继续向上检查进行调整

情况2:cur为红,p为红,g为黑,u不存在或u存在且为黑(单旋+变色解决)

1.如果u不存在,则说明a、b、c、d、e这五棵子树都不存在,这是因为如果c存在的话,则说明c里面存在黑色节点,违反了红黑树第4条性质。g为黑色,p为红色,cur如果不是新插入的节点的话一定是黑色,但如果cur是黑色同样违反红黑树第4条性质,因此cur是新插入的节点

2.如果u存在且为黑色,则说明cur原本的颜色是黑色,由于a或b子树的调整变为了红色

上述两种情况的解决办法:如果p是g的左孩子,cur是p的左孩子,则通过右单旋进行调整;如果p是g的右孩子,cur是p的右孩子,则通过左单旋进行调整。最后将p变为黑色,g变为红色。由于p是黑色,并没有出现连续的红色节点,循环终止
情况3:cur为红,p为红,g为黑,u不存在或u存在且为黑(双旋+变色解决)

这种情况同样是用旋转+变色进行解决,但是用的是双旋。
上图中,首先以p为中心进行左旋,旋转结束后的红黑树与情况2相同,后续步骤可以看情况2
解决方法:如果p为g左孩子,cur为p右孩子,则针对p进行左旋,后续与情况2一致;如果p为g右孩子,cur为p左孩子,则针对p进行右旋,后续与情况2一致
4.3插入的代码
template
class RBTree
{typedef RBTreeNode Node;
public:bool insert(const T& val){if (_root == nullptr){_root = new Node(val);_root->_col = Black;return true;}else{Node* parent = nullptr;Node* cur = _root;while (cur){parent = cur;if (val > cur->_val){cur = cur->_right;}else if (val < cur->_val){cur = cur->_left;}else{return false;}}cur = new Node(val);if (cur->_val > parent->_val){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == Red){Node* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;if (uncle && uncle->_col == Red){grandparent->_col = Red;parent->_col = uncle->_col = Black;//继续向上检查cur = grandparent;parent = cur->_parent;}else //(uncle == nullptr || uncle->_col == Black){//cur是parent的左孩子,单旋调整if (cur == parent->_left){RotateR(grandparent);parent->_col = Black;grandparent->_col = Red;}else //cur是parent的右孩子,双旋调整{RotateL(parent);RotateR(grandparent);cur->_col = Black;grandparent->_col = Red;}break;}}else{Node* uncle = grandparent->_left;if (uncle && uncle->_col == Red){grandparent->_col = Red;parent->_col = uncle->_col = Black;cur = grandparent;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandparent);parent->_col = Black;grandparent->_col = Red;}else{RotateR(parent);RotateL(grandparent);cur->_col = Black;grandparent->_col = Red;}break;}}}_root->_col = Black;return true;}}
private:void RotateR(Node* parent){Node* subl = parent->_left;Node* sublr = subl->_right;Node* pparent = parent->_parent;parent->_left = sublr;if (sublr)sublr->_parent = parent;subl->_right = parent;parent->_parent = subl;if (_root == parent){_root = subl;subl->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subl;}else{pparent->_right = subl;}subl->_parent = pparent;}}void RotateL(Node* parent){Node* subr = parent->_right;Node* subrl = subr->_left;parent->_right = subrl;subr->_left = parent;Node* pparent = parent->_parent;parent->_parent = subr;if (subrl)subrl->_parent = parent;if (_root == parent){_root = subr;subr->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subr;}else{pparent->_right = subr;}subr->_parent = pparent;}}Node* _root = nullptr;
};
5.红黑树的验证
对其进行检查主要是检查每条路径上的黑色节点数相同和没有连续的红色节点
bool CheckColour(Node* root, int blacknum, int benchmark)
{if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_col == Black){++blacknum;}if (root->_col == Red && root->_parent && root->_parent->_col == Red) //检查是否出现连续的红色节点{cout << root->_val << "出现连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);
}
bool _IsBalance(Node* root)
{if (root == nullptr)return true;if (root->_col != Black) //根节点必须是黑色{return false;}//设立一个基准值用来存储该树中其中一条路径黑色节点的数量,再与其他路径的黑色节点树比较,只要有其中一条与其不同,就说明该树有问题。不需要管基准值中存储的数据是否正确int benchmark = 0; //基准值Node* cur = _root;while (cur){if (cur->_col == Black)++benchmark;cur = cur->_left;}return CheckColour(root, 0, benchmark);
}
bool IsBalance()
{return _IsBalance(_root);
}
6.红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是,红黑树不追求绝对的平衡,它是相对平衡,其只需要保证最长路径不超过最短路径的二倍即可,与AVL树相比减少了插入和旋转次数,所以红黑树的性能比AVL树更好,且红黑树的实现更为简单。