上一节我们学习的是AVL树,AVL树是一棵几乎完美接近平衡的树,但是map和set底层的封装用的都是红黑树,可见红黑树相比较AVL树有着更突出的优势。现在让我们看一看红黑树的底层原理以及具体的代码实现吧。
红黑树的概念:
红黑树的性质:
1.从任何一个结点开始到任何一条路径的叶子节点的黑色结点的数量是相同的。
2.整棵树中没有相邻的红色结点。一个结点是红色,那它相邻的孩子结点必定是黑色的。
3.根结点是黑色的。
4.每个叶子结点都是黑色的。(此处的叶子节点指的是空结点)
红黑树结点的定义:
enum Colour
{
RED,
BLACK,
};
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
{}
};
在构造函数中,为什么把颜色默认设置成红色?
因为红色是一种安全的做法,如果插入的结点是黑色的,那么它必定违反性质1。而插入红色结点不一样,如果插入节点的父节点是黑色的,那么插入就不会违反上面性质。所以这是一个概率问题,很明显插入黑色结点出现问题的概率比插入红色结点要高得多。
红黑树的插入:
红黑树的插入和AVL树的插入条件一样,它保持了原来AVL树的特性:
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
pair<iterator,bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = Black;
return make_pair(iterator(_root), true); //make_pair方式返回
}
Node* parent = nullptr;
Node* cur = _root;
KeyofT kot;
while (cur)
{
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) >kot( data))
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(iterator(cur), false);
}
}
cur = new Node(data);
Node* newnode = cur;
cur->_col = Red;
if (kot(parent->_data) > kot(cur->_data))
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
private:
Node* _root=nullptr;
}
数据插进去以后,因为我们插入的是红色结点,所以要进行二叉树的平衡。如果该红色结点的双亲是黑色结点,那么不违反条件不需要进行平衡。如果双亲结点为红色结点,那么就违反了有连续红节点的规则,所以要对它进行平衡处理:
情况一:cur为红,p为红,u为红,g为黑。
如果g为根节点,调整完成后g本应为红,但是根节点的性质是根节点为黑色,所以将g变成黑。
如果g不为根节点,此时g的结点颜色为红,当g的双亲也为红色时就需要继续向上调整。
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
情况二我们需要分情况讨论:u存在或者u不存在。但是u存在那一定是黑色结点。
处理方法:以p为轴心进行一次右单旋,右单旋过后p结点变成黑色,g结点变成红色。平衡以后的二叉树图如下:
相反如果p在右u在左,cur在p的右侧,则以p为轴心进行一次左单旋。
情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑
情况三我们发现上图的g、p、cur成一个折线形,且p是凸出来的,所以先以p为轴心进行一次左单旋,再以g为中心进行一次右单旋。平衡代码如下:
//调整平衡,并且更新颜色
while (parent && parent->_col == Red)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == Red)
{
parent->_col = uncle->_col = Black;
grandfather->_col = Red;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (parent->_left == cur)
{
RotateR(grandfather);
parent->_col = Black;
grandfather->_col = Red;
}
else if (parent->_right == cur)
{
RotateL(parent);
RotateR(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
else
{
assert(false);
}
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 (parent->_right == cur)
{
RotateL(grandfather);
parent->_col = Black;
grandfather->_col = Red;
}
else if (parent->_left == cur)
{
RotateR(parent);
RotateL(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
else
{
assert(false);
}
break;
}
}
}
_root->_col = Black;
return make_pair(iterator(newnode), true);
}
右左单旋的代码在AVL章节中就有,这里我们就不在这里详细展示了。
红黑树实现的完整代码其实并非很重要,我们只需要知道红黑树的性质以及它进行调节平衡的各种情况,接下来我们来谈一谈红黑树平衡的验证方法:
bool IsRBTree()
{
//空树也符合红黑树
if (_root == nullptr)
{
return true;
}
//验证根节点是否为黑色
if (_root->_col == Red)
{
cout << "根节点为红色,不符合规则" << endl;
return false;
}
int ref = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == Black)
{
++ref;
}
cur = cur->_left;
}
return _IsRBTree(_root, 0, ref);
}
bool _IsRBTree(Node* root, int count, int ref)
{
if (root == nullptr)
{
if (count != ref)
{
cout << "每条路径的黑色节点数不相等" << endl;
return false;
}
return true;
}
if (root->_col == Black)
{
++count;
}
Node* ppnode = root->_parent;
if (ppnode && root->_col == Red && ppnode->_col == Red)
{
cout << "存在相连的红色节点" << endl;
return false;
}
return _IsRBTree(root->_left, count, ref) && _IsRBTree(root->_right, count, ref);
}
红黑树的讲解就到这里,感谢大家的阅读以及支持!