概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
- 每个结点不是红色就是黑色
- 根结点是黑色
- 树不能出现连续的红色结点
- 同一层的结点的路径(这里的路径是指该结点到null结点的路径),包含相同数量的黑色结点
- 每一个null结点都是黑色的
为什么上面的逻辑可以控制高度使搜索树平衡?
基于条件3和条件4,我们可以推出最短路径肯定是全黑结点最长路径肯定是红黑相间的路径,保证树的左右子树高度高度在h~2h之间,限定了高度差不超过2。如图:
红黑树与AVL树的区别?
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
红黑树节点的定义
enum color
{
RED,
BLACK
};//枚举的使用必须先初始化
template<class Value>
struct RBTreeNode
{
RBTreeNode(const Value& data)
:_leftNode(nullptr)
, _rightNode(nullptr)
, _parentNode(nullptr)
, _data(data)
,_col(RED)
{
}
Value _data;
RBTreeNode<Value>* _leftNode;
RBTreeNode<Value>* _rightNode;
RBTreeNode<Value>* _parentNode;
color _col;
};
标准的三叉链(左右子树指针和父结点指针),模版的含义后面封装map,set的时候再详述
红黑树的插入调整讲述
前面是搜索树的插入逻辑,与之前搜索树不同的是后面的根据红黑色节点的旋转调整。
首先,我们的新增结点颜色一定是红色,为什么呢?
因为插入红色,对于整棵树的影响比较小新增插入黑色节点会影响所有路径:-规则4
新增插入红色节点会影响父亲结点-规则3
检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,约定:cur为当前节点,pcur为父节点,gcur为祖父节点,ucur为叔叔节点
此时需要对红黑树分情况来讨论:
情况一: cur为红,p为红,g为黑,u存在且为红
解决方案:把pcur和ucur变成黑色,把gcur变成红色,然后cur = gcur继续往上调整,在代码层面,迭代时用cur和pcurj,直到pcur为黑色或者走到根结点。
对于根节点的处理:
如上图中的第一棵树,在调整中会把根节点调成红色,这显然不符合根节点的定义,对于这情况只需在逻辑退出后把根节点调整为黑即可。
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
解决方案:
1.右单旋:p为g的左孩子,cur为p的左孩子,则进行右单旋转,p、g变色–p变黑,g变红
2.左单旋:p为g的右孩子,cur为p的右孩子,则进行左单旋转,p、g变色–p变黑,g变红
3.左右单旋:p为g的左孩子,cur为p的右孩子,则先进行左单旋,再右单旋
4.右左单旋:p为g的右孩子,cur为p的左孩子,则先进行右单旋,再左单旋
该图为右单旋
写旋转的技巧:
1.先想象简单的三个点,然后有4种情况,如图:
2.然后再去延伸到有左右子树和该树作为子树的思路去延伸(三角形为抽象树,把他想象成一个子树即可)
把新增结点设为cur,新增结点的父结点设为pcur,还有pcur父结点gcur,对于左旋转来说,需要操作的结点为gcurParent(祖父结点的父结点),用于旋转后让cur继续于上面的树保持链接,pcur结点,cur结点,还有cur结点的右子树,因为要连接到pcur结点左子树,而pcur结点要变成cur结点右子树(详细的选择逻辑在我AVL树的文章里面)所以要操作的结点为 gcur、pcur、gcurParent、pcurRight。右旋转就是gcur、pcur、gcurParent、pcurLeft
左转和右转的快速判断:
gcur移动到左边就是左转,其目的是右边较高的部分移到左边,使得树平衡gcur移动到右边就是右转,其目的是左边较高的部分移到右边,使得树平衡
class RBTree
{
public:
pair<iterator,bool> insert(const V& data)
{
//找位置
//BST的插入逻辑(右大左小)
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root), true);
}
Node* pcur = nullptr;
Node* cur = _root;
KeyOfT kot;
while (cur)
{
pcur = cur;//记录cur结点的父结点
if (kot(data) > kot(cur->_data))
{
cur = cur->_rightNode;
}
else if (kot(data) < kot(cur->_data))
{
cur = cur->_leftNode;
}
else//重复的不让插入
{
return make_pair(iterator(cur), false);
}
}
cur = new Node(data);
Node* newnode = cur;//用于返回
//链接
cur->_parentNode = pcur;//孩子结点连接父结点
//父亲结点连接孩子结点
if (cur->_data.first > pcur->_data.first)
{
pcur->_rightNode = cur;
}
else
{
pcur->_leftNode = cur;
}
//如果cur节点和pcur节点都为红色--调整
while (pcur && pcur->_col == RED)
//判断pcur是否为空,避免对空指针的解引用
{
//cout << data.first << endl;
//获取ucur和gcur
Node* ucur = nullptr;
Node* gcur = pcur->_parentNode;
if (gcur->_leftNode != pcur)
{
ucur = gcur->_leftNode;
}
else
{
ucur = gcur->_rightNode;
}
//情况一:
if (ucur && ucur->_col == RED)
{
pcur->_col = ucur->_col = BLACK;
gcur->_col = RED;
cur = gcur;
pcur = cur->_parentNode;
}
else if(ucur == nullptr || ucur->_col == BLACK)
{
//判断gcur, pcur, cur之间的连接情况
if (gcur->_leftNode == pcur && pcur->_leftNode == cur) //右单旋
{
_rotateR(gcur,pcur);
}
else if (gcur->_rightNode == pcur && pcur->_rightNode == cur) //左单旋
{
_rotateL(gcur, pcur);
}
else if (gcur->_leftNode == pcur && pcur->_rightNode == cur) //右左旋
{
_rotateL(pcur, cur);
//第一次旋转后pcur发生了变换 cur才是pcur
_rotateR(gcur, cur);
}
else if (gcur->_rightNode == pcur && pcur->_leftNode == cur) //左右旋
{
_rotateR(pcur, cur);
_rotateL(gcur, cur);
}
else
{
cout << data.first << endl;
assert(false);
}
}
else
{
assert(false);
}
_root->_col = BLACK;
}
return make_pair(iterator(newnode), true);
}
private:
Node* _root = nullptr;
void _rotateR(Node* gcur,Node* pcur)
{
Node* gcurParent = gcur->_parentNode;
Node* pcurRight = pcur->_rightNode;
//修改parentNode
pcur->_parentNode = gcurParent;
gcur->_parentNode = pcur;
if(pcurRight)
pcurRight->_parentNode = gcur;
//修改leftNode和rightNode
pcur->_rightNode = gcur;
gcur->_leftNode = pcurRight;
if (gcurParent == nullptr)
{
_root = pcur;
_root->_parentNode = nullptr;
}
else if (gcurParent->_leftNode == gcur)
{
gcurParent->_leftNode = pcur;
}
else
{
gcurParent->_rightNode = pcur;
}
pcur->_col = BLACK;
gcur->_col = RED;
}
void _rotateL(Node* gcur, Node* pcur)
{
Node* gcurParent = gcur->_parentNode;
Node* pcurLeft = pcur->_leftNode;
//修改parentNode
pcur->_parentNode = gcurParent;
gcur->_parentNode = pcur;
if(pcurLeft)
pcurLeft->_parentNode = gcur;
//修改leftNode和rightNode
pcur->_leftNode = gcur;
gcur->_rightNode = pcurLeft;
if (gcurParent == nullptr)
{
_root = pcur;
_root->_parentNode = nullptr;
}
else if(gcurParent->_leftNode == gcur)
{
gcurParent->_leftNode = pcur;
}
else
{
gcurParent->_rightNode = pcur;
}
pcur->_col = BLACK;
gcur->_col = RED;
}