红黑树(Red Black Tree) 是一种自平衡二叉查找树,在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
AVL树 VS 红黑树
红黑树是一种特化的AVL树,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
AVL树要求每棵子树的左右高度差不超过1,是严格平衡;而红黑树要求最长路径不超过最短路径的2倍,是接近平衡。
而红黑树是一种AVL树的变体,它要求最长路径不超过最短路径的2倍,左右子树高差有可能大于 1。所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
相对而言,插入或删除同样的数据,AVL树旋转的更多,而红黑树则旋转的更少效率相对较高。
红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 结点是红色或黑色。
性质2. 根结点是黑色。
性质3. 每个红色结点的两个子结点都是黑色。(每条路径上不能有两个连续的红色结点)
性质4. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。 (每条路径上的黑色节点数量相同)
性质5. 所有NIL结点都是黑色的。(NIL节点即空结点)
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
是性质3导致路径上不能有两个连续的红色结点确保了这个结果。最短的可能路径都是黑色结点,最长的可能路径有交替的红色和黑色结点。因为根据性质4所有路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长。
思考:新插入的节点应该设为黑色还是红色?
如果将新插入的节点设为黑色,不管插到那条路径都必然违反性质4。
如果将新插入的节点设为红色:如果父节点是红色则违反性质3,需要进行调整;如果父节点是黑色就正常插入,无需调整。
对比两种情况,最终选择将新插入的节点设为红色。
enum Color{
RED,
BLACK
};
template <class K, class V>
struct RBTreeNode{
RBTreeNode<K,V> *_left;
RBTreeNode<K,V> *_right;
RBTreeNode<K,V> *_parent;
pair<K,V> _kv;
Color _color; //颜色属性,红或黑
RBTreeNode(const pair<K,V> &kv=pair<K,V>(), Color color = RED)
:_left(nullptr),
_right(nullptr),
_parent(nullptr),
_kv(kv),
_color(color)
{}
};
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
按照二叉搜索的树规则插入新节点
检测新节点插入后,红黑树的性质是否造到破坏。因为新节点的默认颜色是红色,因此:
在讲解情况三、四、五之前,先说明一下:
- cur为当前节点(关注节点),p(parent)为父节点,g(grandparent)为祖父节点,u(uncle)为叔叔节点;
- cur不一定就是新插入的节点,也有可能是因为 cur 的子树在调整的过程中将 cur 节点的颜色由黑色改成红色。
情况一: cur为红,p为红,g为黑,u存在且为红
抽象分析:
解决方式:变色并继续向上调整
具体分析:
cur就是新插入的节点:
cur节点原来是黑色之后又被调整为红色:
注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋)
解决方式:单旋+变色
具体分析:u 的情况有两种
uncle节点不存在:
如果 u 节点不存在,则 cur 一定是新插入节点,因为如果 cur 不是新插入节点,则 cur 和 p 一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
uncle节点存在且为黑色:
如果 u 节点存在且为黑色,那么 cur 节点原来的颜色也一定是黑色的,现在看到其是红色的原因是因为 cur 的子树在调整的过程中将 cur 节点的颜色由黑色改成红色。
注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋)
解决方式:双旋+变色
具体分析:
uncle节点不存在
uncle节点存在且为黑色:
注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。
总结:
template <class K, class V>
bool RBTree<K,V>::Insert(const pair<K,V> &kv)
{
//1.按照二叉搜索的树规则插入新节点
if(_root == nullptr)
{
_root = new Node(kv, BLACK); //性质2:根节点是黑色的
return true;
}
Node *cur = _root;
Node *parent = nullptr;
while(cur != nullptr)
{
if(kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if(kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else{
return false;
}
}
cur = new Node(kv,RED); //新插入的节点是红色的
if(kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else{
parent->_left = cur;
}
cur->_parent = parent;
//2.检测新节点插入后,红黑树的性质是否造到破坏。
//如果上一次循环中grandparent为根节点,此次循环parent == nullptr,结束调整。
//如果其父节点的颜色是黑色,没有违反红黑树的任何性质,则不需要调整;
while(parent != nullptr && parent->_color == RED)
{
Node *grandparent = parent->_parent;
//增加断言,是为了方便找出一般错误。
assert(grandparent != nullptr); //因为父节点是红色的,所以祖父节点一定不为空,性质2
assert(grandparent->_color == BLACK); //因为父节点是红色的,所以祖父节点一定是黑色,性质3
Node *uncle = grandparent->_left;
if(parent == grandparent->_left)
uncle = grandparent->_right;
if(uncle != nullptr && uncle->_color == RED) //情况一:uncle存在且为红
{
//p,u变黑,g变红,继续向上调整。
parent->_color = uncle->_color = BLACK;
grandparent->_color = RED;
cur = grandparent;
parent = cur->_parent;
}
else //情况二、三:uncle不存在或uncle存在且为黑
{
//需要进行旋转变色处理,先要判断旋转方式。
if(parent == grandparent->_left)
{
if(cur == parent->_left) //左左
{
RotateR(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
}
else{ //左右
RotateL(parent);
RotateR(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
}
}
else{
if(cur == parent->_right) //右右
{
RotateL(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
}
else{ //右左
RotateR(parent);
RotateL(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
}
}
break; //完成旋转变色后每条路径的黑节点数量相同且根节点为黑色不需要继续往上处理。
} //end of else
} //end of while
//如果上一次循环中grandparent为根节点,循环结束后要将根节点再改为黑色,性质2。
if(cur == _root)
cur->_color = BLACK;
return true;
}
红黑树的检测分为两步:
bool IsValidRBTree(){
//空树也是红黑树
if(_root == nullptr) return true;
//检查性质2:
if(_root->_color != BLACK)
{
cout << "违反性质2:根节点不为黑色!" << endl;
return false;
}
//检查性质3,4:
int benchmark = 0;
return PrevCheck(_root, 0, benchmark);
}
//blacknum:用于记录当前路径的黑色节点个数,不能传引用。
//benchmark:用于记录第一条路径的黑色节点个数。需要传引用,返回给上层递归。
bool _IsValidRBTree(Node *root, int blacknum, int &benchmark){
if(root == nullptr)
{
if(benchmark == 0) //表示第一条路径遍历完
{
benchmark = blacknum; //记录第一条路径的黑色节点个数
return true;
}
else{
if(blacknum != benchmark) //如果其他路径的blacknum与第一条路径不同,说明违反性质4
{
cout << "违反性质4:从任意节点到每个叶子节点的所有路径都包含相同数目的黑色节点!" << endl;
return false;
}
else{
return true;
}
}
}
//检查性质3:
if(root->_color == RED && root->_parent->_color == RED)
{
cout << "违反性质3:路径上有两个连续的红色节点!" << endl;
return false;
}
if(root->_color == BLACK)
{
++blacknum;
}
return PrevCheck(root->_left, blacknum, benchmark)
&& PrevCheck(root->_right, blacknum, benchmark);
}