概念
红黑树是一种二叉搜索树,但是在每个结点上增加一个表示该点颜色的存储位,可以是Red或者Black,通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,相对于AVL树来说,红黑树是接近平衡。
红黑树的性质
- 每一个结点不是黑色的就是 红色的
- 树的根结点是黑色的
- 如果一个结点是红色的,那么该结点的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(给出空结点易于标记路径)
上述性质总结下来也就是三条
- 红黑树的根是黑色的
- 红黑树没有连续的红色结点
- 每一条路径上都着相同的黑色结点
红黑树的简单实现
红黑树结点的定义
在红黑树的结点中,我们需要指向左右孩子的指针,由于红黑树调整时还需要进行旋转,为了实现简单还需要一个指向父亲节点的指针,接着由于实现的是关联式容器,所以需要存储键值对,最后我们还需要一个变量来存储该结点的颜色,为了实现方便创建一个枚举类型。
enum Color
{
Black,
Red
};
template<class K, class V>
struct BRTreeNode
{
BRTreeNode<K, V>* _left;
BRTreeNode<K, V>* _right;
BRTreeNode<K, V>* _parent;
pair<K, V> _kv;
Color _col;
BRTreeNode(const pair < K, V> kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(Red)
{}
};
红黑树的插入
红黑树新结点的插入分为两步
首先,按照二叉搜索树的规则插入新结点,如果是头结点,按照红黑树的性质,将头节点的颜色设为黑色,否则,对于新增的结点,我们设其默认颜色为红色。(若为黑色,则包含该结点的所有路径上都会多出来一个黑色节点,不包含的则不会增加,根据红黑树的性质,每一条路径上的黑丝结点数必须相等,此时则需要调整其他相关路径上的黑色结点数量会很麻烦,而如果是红色的话,则调整容易很多)
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = Black;//插入一个根结点之后先把根节点的颜色变成黑色
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//找到要插入的位置了,准备插入
cur = new Node(kv);
if (parent->_kv.first<kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//新增结点红色的
cur->_col = Red;
检测新结点插入后,红黑树的性质是否遭到了破坏,由于新插入的结点的颜色是红色,如果其父亲结点的颜色是黑色,则满足红黑树的性质,不需要调整。但当新插入的结点的父亲结点颜色是红色的话,就违反了性质(不能有连在一起的红色),此时需要对红黑树分情况来讨论:
假定cur为当前节点,p为当前结点的父亲结点,u为当前结点的叔叔结点,g为祖父结点,我们先讨论p为g的左孩子的情况
情况一、cur为红,p为红,g为黑,u存在且其为黑,抽象图如下所示
这里的abcde是抽象出来的子树 ,也可以将其高度视为0,此时cur就是新插入的结点,否则cur就是更新上去的结点。此时为了保持红黑树的性质,可以考虑将其父亲结点变为黑,此时仍不满足红黑树的性质,还需将其叔叔结点变黑,如果此时g结点是根结点,则满足红黑树的性质,如果不是根结点的话则为了平衡还需要把g结点变成红色,再将g结点作为新的cur不断向上处理。
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == Red)
{
parent->_col = uncle->_col = Black;
grandfather->_col = Red;
cur = grandfather;
parent = cur->_parent;
}
情况二、cur为红,p为红,g为黑,u不存在或者u为黑,cur为p的左子树
首先看u不存在的情况
考虑图左这种情况,如果子树c存在的话,则子树c中至少有一个结点,无论这个结点是黑色还是红色,都不满足红黑树的性质。所以c子树一定不存在,当c不存在时,同理ab子树也不存在。所以应该是中间图的情景,cur为新插入的结点,此时将p点变为黑色,g点变为红色。
此时处理方法为对g结点进行右单旋后,交换g和p结点的颜色(如右图)
接着看u存在且为黑的情况
此时abcde子树一定不为空,所以cur一定不是新插入的结点,也有可能是第一种情况不断向上调整所导致的,也就是说cur结点原来是黑色的,因为新结点的插入导致其子树不满足红黑树的性质,通过不断向上调整之后变成红色。
如上图,此时从g结点到其所有后代叶结点的路径上的黑色结点数都相等,但是有着连续的红色结点,因为cur是刚调整为红,所以考虑将p结点变黑,为了保持黑色结点相等性质,还需把g结点变红。如上图所示,但是此时右子树又会少一个黑色结点,所以我们将此种情况和u不存在的情况合并,都对g点进行右单旋,再交换p和g点的颜色(如下图)
情况三、cur为红,p为红,g为黑,u不存在或者u为黑,cur为p的右子树
观察如图所示的情况三,其与情况二差不多,我们首先考虑是否能通过某种操作将其转换为情况二,然后合并在一起讨论。
对p点进行左单旋,再交换cur和p的指针,得到如下图所示,可以看到其已经转换成了情况二
代码实现
if (cur == parent->_right)
{
//双旋情况三,因为双旋处理一下就变成单旋,所以我们先将其单选之后,再将他和后面的一种情况合起来讨论
RotateL(parent);
swap(parent, cur);//为了变成和第二种情况一样。
}
//第二种情况(有可能是第三种变过来de)
RotateR(grandfather);
grandfather->_col = Red;
parent->_col = Black;
对于p为g的右孩子的情况,只需要将g的左右孩子交换即可。
左右单旋
左右单旋的实现在我实现AVL树的时候已经实现了,详情可以看我上一篇博客https://blog.csdn.net/newbie5277/article/details/140419016
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* pparent = parent->_parent;//记录parent的父亲结点
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)//分情况讨论,如果parent是头节点
{
_root = subR;
subR->_parent = nullptr;
}
else//parent是非头节点
{
if (parent == pparent->_left)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
}
查找
Node* Find(const K& k)
{
Node* cur = _root;
if (_root == nullptr)
return nullptr;
while (cur)
{
if (cur->_kv.first < k)
cur = cur->_right;
else if (cur->_kv.first > k)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
完整代码
#pragma once
#include<iostream>
#include<vector>
#include<random>
#include<time.h>
using namespace std;
enum Color
{
Black,
Red
};
template<class K, class V>
struct BRTreeNode
{
BRTreeNode<K, V>* _left;
BRTreeNode<K, V>* _right;
BRTreeNode<K, V>* _parent;
pair<K, V> _kv;
Color _col;
BRTreeNode(const pair < K, V> kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(Red)
{}
};
template<class K,class V>
class BRTree
{
typedef BRTreeNode<K, V> Node;
public:
void _Inorder(Node* root)//递归调用
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << " ";
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
Node* Find(const K& k)
{
Node* cur = _root;
if (_root == nullptr)
return nullptr;
while (cur)
{
if (cur->_kv.first < k)
cur = cur->_right;
else if (cur->_kv.first > k)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
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* pparent = parent->_parent;//记录parent的父亲结点
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)//分情况讨论,如果parent是头节点
{
_root = subR;
subR->_parent = nullptr;
}
else//parent是非头节点
{
if (parent == pparent->_left)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
}
bool Insert(const pair<K,V> kv)
{
//先按二叉搜索树的规则插入
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = Black;//插入一个根结点之后先把根节点的颜色变成黑色
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (_root->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (_root->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//找到要插入的位置了,准备插入
cur = new Node(kv);
if (parent->_kv.first<kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//新增结点红色的
cur->_col = Red;
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 = uncle->_col = Black;
grandfather->_col = Red;
//接着继续往上处理,父亲也有可能不存在,所以在开始的时候要加一个判断parent是否存在的条件
cur = grandfather;
parent = cur->_parent;
}
else//情况二、叔叔结点不存在或者叔叔结点存在且为黑
{
if (cur == parent->_right)
{
//双旋情况三,因为双旋处理一下就变成单旋,所以我们先将其单选之后,再将他和后面的一种情况合起来讨论
RotateL(parent);
swap(parent, cur);//为了变成和第二种情况一样。
}
//第二种情况(有可能是第三种变过来de)
RotateR(grandfather);
grandfather->_col = Red;
parent->_col = Black;
break;
}
}
else
{
//反方向的代码
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == Red)
{
parent->_col = uncle->_col = Black;
grandfather->_col = Red;
cur = grandfather;
parent = cur->_parent;
}
else
{
//二三种情况合起来讨论
if (cur == parent->_left)
{
RotateR(parent);
swap(cur, parent);//交换的是节点的指针
}
RotateL(grandfather);
grandfather->_col = Red;
parent->_col = Black;
}
}
}
_root->_col = Black;
return true;
}
private:
Node* _root = nullptr;
};
void BRTree_test()
{/*
vector<int, int> v;
int r=srand(time(0));*/
/*int a[] = { 16,3,7,11,9,26,18,14,15 };*/
int a[] = { 4,2,6,1,3,5,15,7,16,14 };
BRTree<int, int> rb;
for (auto e : a)
{
rb.Insert(make_pair(e, e));
}
rb.Inorder();
}