目录
红黑树的概念
红黑树,是一种二叉搜索树,在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
当满足了这些性质后为什么红黑树确保没有一条路径会比其他路径长出俩倍?
因为最短的路径无非就是全部是黑色节点,最长的路径则是在每两个黑节点之间添加一个红色节点,因此最长的路径可能会等于最短路径的两倍,但绝对不会大于
红黑树节点定义
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
{
_left = nullptr;
_right = nullptr;
_parent = nullptr;
_col = Red;
}
};
红黑树的结构
库中红黑树的实现中增加一个头结点,因为根节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 Parent 域指向红黑树的根节点,Left域指向红黑树中最小的节点,Right域指向红黑树中最大的节点
红黑树的插入
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
情况1:cur为红,p为红,g为黑,u存在且为红
这种情况我们只变色,不旋转,把p和u变为黑,g变为红
if (uncle && uncle->_col == Red)//叔叔为红
{
parent->_col = uncle->_col = Black;
grandfather->_col = Red;
cur = grandfather;
parent = cur->_parent;
}
情况2.1:叔叔不存在或者叔叔存在且为黑,且cur和p的关系与p和g的关系一样,是直线
(这种情况一定是由情况1变过来的,cur原来一定是黑色)
else//叔叔不存在或者存在且为黑,需要旋转+变色
{
if (cur == parent->_left)//右单旋+变色
{
RotateR(grandfather);
grandfather->_col = Red;
parent->_col = Black;
}
情况2.1,其他一样,出现折线。则需要双旋+变色
else//cur在parent右边了,出现折线、双旋
{
RotateL(parent);
RotateR(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
插入总代码:
bool insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = Black;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_col = Red;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent && parent->_col == Red)//有重复红色
{
Node* grandfather = parent->_parent;
assert(grandfather);//理论上不可能出现,这种情况parent是根,应该是黑色
if (grandfather->_left == parent)//爸爸在左
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == Red)//叔叔为红
{
parent->_col = uncle->_col = Black;
grandfather->_col = Red;
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在或者存在且为黑,需要旋转+变色
{
if (cur == parent->_left)//右单旋+变色
{
RotateR(grandfather);
grandfather->_col = Red;
parent->_col = Black;
}
else//cur在parent右边了,出现折线、双旋
{
RotateL(parent);
RotateR(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
break;//不会影响上层黑色节点数目,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->_right)//左单旋+变色
{
RotateL(grandfather);
grandfather->_col = Red;
parent->_col = Black;
}
else//cur在parent右边了,出现折线、双旋
{
RotateR(parent);
RotateL(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
break;//不会影响上层黑色节点数目,break,不需要再向上处理;
}
}
}
_root->_col = Black;
return true;
}
红黑树的验证
bool IsBalanceTree()
{
// 检查红黑树几条规则
Node* pRoot = _root;
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (Black != pRoot->_col)
{
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数 -- 比较基准值
size_t BlackCount = 0;
Node* pCur = pRoot;
while (pCur)
{
if (Black == pCur->_col)
BlackCount++;
pCur = pCur->_left;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, BlackCount);
}
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t BlackCount)
{
//走到null之后,判断k和Black是否相等
if (nullptr == pRoot)
{
if (k != BlackCount)
{
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
}
return true;
}
// 统计黑色节点的个数
if (Black == pRoot->_col)
k++;
// 检测当前节点与其双亲是否都为红色
if (Red == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == Red)
{
cout << "违反性质三:存在连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->_left, k, BlackCount) &&
_IsValidRBTree(pRoot->_right, k, BlackCount);
}
红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2N),红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红
黑树更多。