红黑树
一、红黑树是什么
红黑树也是一种二叉搜索树,只是给每个结点增加了一个存储位去存储颜色,通过对树中各结点之间颜色的限制,并且最长路径比最短路径的两倍短,确保其接近平衡。
二、红黑树的性质
1.结点的颜色只有红色或者黑色
2.根结点一定是黑色
3.一个结点为红色,则其的两个孩子结点为黑色(前提是存在孩子结点)
4.每个结点到叶子结点的简单路径上,均包含相同的黑色结点
5.叶子节点(这里指的空的叶子节点)为黑色
三、实现红黑树
3.1红黑树的定义
首先我们需要知道红黑树也是一种二叉搜索树,所以每个结点的存储跟二叉搜索树相似,包括左右指针,父亲指针,颜色以及存储的数据。
enum Color{
BLACK,
RED
};
template<class K, class V>
struct RBTreeNode{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Color _color;
pair<K, V>_kv;
RBTreeNode(const pair<K, V>kv = pair<K, V>())
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _color(RED)
, _kv(kv)
{}
};
这里我们给定每个节点的初始颜色为红色(插入的节点,除了根结点,初始都应为红色),存储的为键值对数据,是为了后面各项操作的更好的完成。
在创建红黑树时,我们也初始化了它的结构,我们为了让其跟set和map接口的底层结构相同(具体为了方便迭代器的实现),所以我们定义了一个头结点。让其左指针指向红黑树的最左节点,右指针指向红黑树的最右节点。
template<class T, class V>
class RBTree{
public:
typedef RBTreeNode<T, V> Node;
RBTree()
:_header(new Node)
{
_header->_left = _header->_right = _header; //初始化
}
private:
Node* _header; //头节点
}
3.2红黑树的插入
3.2.1插入
在定义中我们知道,红黑树也是一种二叉搜索树,所以插入的方式也和二叉搜索树相似,通过键值对中的Key去进行插入
bool Insert(const pair<T, V>& val){
//空树
if (_header->_parent == nullptr){
Node* cur = new Node(val);
_header->_left = _header->_right = cur; //头结点
cur->_parent = _header;
_header->_parent = cur; //头结点的父节点为根节点
cur->_color = BLACK; //根结点必须为黑色
return true;
}
//非空(搜索树的插入)
Node* parent = nullptr;
Node* cur = _header->_parent;
while (cur){
parent = cur;
if (cur->_kv.first > val.first){
cur = cur->_left;
}
else if (cur->_kv.first < val.first){
cur = cur->_right;
}
else{
return false;
}
}
cur = new Node(val);
if (val.first < parent->_kv.first){
parent->_left = cur;
}
else{
parent->_right = cur;
}
cur->_parent = parent;
return true;
}
这里只是单单通过键值对中的key值去走了一个二叉搜索树的插入,插入之后,我们需要去调整搜索树的颜色,使它变成一个红黑树。
3.2.2颜色调整
根据红黑树的性质,我们可以知道,红黑树中红色节点不可以连续,所以调整颜色就要从红色连续的节点出发,调整颜色分为一下情况,如图:
情况一:当插入一个节点cur后,它的父节点parent为红色,并且它的叔叔uncle节点为红色.(左边的左边都是红,右边也是红)。
调整方式:父节点parent和叔叔节点uncle变为黑色,将祖父母节点grandparent变为红色。
情况二:当插入一个节点cur后,它的父节点parent为红色,但是它的叔叔uncle节点为黑色或者不存在.(左边的左边都是红,右边为黑或者不存在)
调整方式:以祖父母grandparent节点为根节点进行右旋(右旋在AVL树中讲过,不太了解的读者,可以去阅读我的AVL树对应博客),然后将祖父母节点grandparent变为红色,父节点parent变为黑色。
情况三:当插入一个节点cur后,它的父节点parent为红色,但是它的叔叔uncle节点为黑色或者不存在.(左边的右边都是红,右边为黑或者不存在)
调整方式:以父节点parent为根节点进行左旋旋(左旋在AVL树中讲过,不太了解的读者,也可以去阅读我的AVL树对应博客),然后将当前节点cur和父节点parent的指针调换位置,进行情况而的调整方式即可。
以上我们只说了在左子树上的各种情况,在右子树上类似,读者可以根据一下代码,自行去验证
//调整颜色
while (cur != _header->_parent&&cur->_parent->_color == RED){
Node* parent = cur->_parent;
Node* grandfather = parent->_parent;
if (grandfather->_left == parent){ //左子树上的各种情况
Node* unice = grandfather->_right;
if (unice&&unice->_color == RED){ //情况一
unice->_color = parent->_color = BLACK;
grandfather->_color = RED;
cur = grandfather; //继续向上调整
}
else{
if (parent->_right == cur){ //情况三
RotateL(parent);
swap(cur, parent);
}
RotateR(grandfather); //情况二
parent->_color = BLACK;
grandfather->_color = RED;
break;
}
}
else{ //右子树的各种情况
Node* unice = grandfather->_left; //叔叔节点在祖父母的左边
if (unice&&unice->_color == RED){ //叔叔节点存在
unice->_color = parent->_color = BLACK;
grandfather->_color = RED;
cur = grandfather;
}
else{ //叔叔节点不存在,或者为黑色
if (parent->_left == cur){
RotateR(parent);
swap(parent, cur);
}
RotateL(grandfather); //以祖父节点位根左旋
parent->_color = BLACK;
grandfather->_color = RED;
break;
}
}
}
_header->_parent->_color = BLACK;
_header->_left = leftMost(); //红黑树的最左节点
_header->_right = rightMost(); //红黑树的最右节点
在插入结束后,我们需要去让根节点的颜色变为黑色,并且去调整头节点的指向。
注意:通过三种情况的图片我们呢可以看出,情况一相对于情况二三,最终调整后它的最上边的节点为红色,所以有可能会造成上边红黑树遭到破坏,所以还需要向上继续调整,直到调整到根节点,或者不存在红色连续的节点。
3.3红黑树的迭代器
红黑树每个节点属于物理不连续的,并且每个节点包括很多的区域,所以不能通过原生的迭代器去操作红黑树,需要去通过节点去的内部区域去实现,具体如下:
红黑树迭代器的基本结构:
struct RBTreeIterator{
typedef RBTreeNode<K, V> Node;
typedef RBTreeIterator<K, V> Self;
Node* _node;
RBTreeIterator(Node* node)
:_node(node)
{}
通过操作节点去实现*、->、!=、++、–等操作
pair<K, V>& operator*(){
return _node->_kv;
}
pair<K, V>* operator->(){
return &_node->_kv;
}
bool operator!=(const Self& it){
return _node != it._node;
}
*则是得到数据,->则是得到数据的相应地址,!=则是通过节点的比较就看一判断
最重要的就是++,–操作,首先我们说++,这是一个二叉搜索树,所以在得迭代器++过程中,我们访问的也是一个"有序"的数据,也就相当于中序遍历(左根右),所以在++时,我们首先需要去考虑目前节点是否有右子树,如果存在右子树,则需要去访问右子树的最左节点,如果不存在,则需要去判断该节点是否为它父节点的右节点,如果是,则说明在访问当前节点时,它的父节点已经被访问了,这是就需要调整当前节点的指向,使其指向父节点,让其父节点向上移动,最终让节点位置移动到向上移动后的父节点,如果目前节点不是它父节点的右节点,则访问父节点即可。这里需要注意的是,如果该红黑树没有右子树,则它的头结点的右指针就会指向根,这样会造成节点向上移动的死循环,所以我们需要去给定限制,使其在根节点是结束循环。
下面我们通过图片说明:
具体代码如下:
Self& operator++(){
if (_node->_right){ //该节点右子树存在,则访问右子树的最左节点
_node = _node->_right;
while (_node->_left){
_node = _node->_left;
}
}
else{ //不存在
Node* parent = _node->_parent;
if (parent->_parent != _node){ //限制条件,访问到根节点结束
while (parent->_right == _node){
_node = parent;
parent = parent->_parent;
}
}
_node = parent;
}
return *this;
}
Self& operator--(){
if (_node->_left){ //该节点左子树存在,则访问右子树的最右节点
_node = _node->_left;
while (_node->_right){
_node = _node->_right;
}
}
else{ //不存在
Node* parent = _node->_parent;
if (parent->_parent != _node){
while (parent->_left == _node){
_node = parent;
parent = parent->_parent;
}
}
_node = parent;
}
return *this;
}
红黑树的迭代器实现完成后,最终在红黑树中如下代码实现:
iterator begin(){
return iterator(_header->_left);
}
iterator end(){
return iterator(_header);
}
iterator rbegin(){
return iterator(_header->_right);
}
iterator rend(){
return iterator(_header);
}
3.4红黑树的其他操作
3.4.1简单操作函数
这里我们去实现上边头结点的左右指针指向位置的函数
左指针指向红黑树的最左节点:
Node* leftMost(){
Node* cur = _header->_parent;
while (cur->_left){
cur = cur->_left;
}
return cur;
}
右指针指向红黑树的最右节点:
Node* rightMost(){
Node* cur = _header->_parent;
while (cur->_right){
cur = cur->_right;
}
return cur;
}
还有上边左旋和右旋,由于和AVL树的节点结构不同,所以在这里再写一次:
//右旋
void RotateR(Node* root){
Node* Lcur = root->_left;
Node* LRcur = Lcur->_right;
root->_left = LRcur;
if (LRcur)
LRcur->_parent = root;
Lcur->_right = root;
if (root == _header->_parent){ //这里判断是否为头结点的父节点
_header->_parent = Lcur;
Lcur->_parent = _header;
}
else{
Node* grandparent = root->_parent;
Lcur->_parent = grandparent;
if (grandparent->_left == root)
grandparent->_left = Lcur;
else
grandparent->_right = Lcur;
}
root->_parent = Lcur;
}
//左旋
void RotateL(Node* root){
Node* Rcur = root->_right;
Node* RLcur = Rcur->_left;
root->_right = RLcur;
if (RLcur)
RLcur->_parent = root;
Rcur->_left = root;
if (root == _header->_parent){
_header->_parent = Rcur;
Rcur->_parent = _header;
}
else{
Node* grandparent = root->_parent;
Rcur->_parent = grandparent;
if (grandparent->_left == root)
grandparent->_left = Rcur;
else
grandparent->_right = Rcur;
}
root->_parent = Rcur;
}
3.4.2红黑树的判断
根据红黑树的性质以及定义,要判断一棵树是否为红黑树,首先需要判断它是否为二叉搜索树,所以先通过中序遍历验证:
void inorder(){
Node *_root = _header->_parent;
if (_root){
_inorder(_root);
}
}
void _inorder(Node* root){
if (root){
_inorder(root->_left);
cout << root->_kv.first << "-" << root->_kv.second << "-" << root->_color << endl;
_inorder(root->_right);
}
}
接着就需要去判断是否为红黑树了
1.根节点为黑色
2.不能出现连续红色节点
3.每个结点到叶子结点的简单路径上,均包含相同的黑色结点
代码如下:
bool isRBTree(){
Node* root = _header->_parent;
if (root == nullptr){
return true;
}
if (root->_color == RED){ //1
cout << "root not black" << endl;
return false;
}
//计算一条路径黑色节点个数
int black_num = 0;
Node* cur = root;
while (cur){
if (cur->_color == BLACK){
black_num++;
}
cur = cur->_left;
}
int k = 0;
return _isRBTree(root, k, black_num);
}
bool _isRBTree(Node* root, int k, int black_num){
if (root == nullptr){
if (k == black_num){ //3
return true;
}
cout << "black not enough" << endl;
return false;
}
if (root->_color == BLACK){
k++;
}
if (root->_parent != nullptr&&root->_color == RED&&root->_parent->_color == RED){ //2
cout << "red continuous" << endl;
return false;
}
return _isRBTree(root->_left, k, black_num) && _isRBTree(root->_right, k, black_num);
}