红黑树是一种自平衡的二叉搜索树,它在插入和删除操作时会通过一系列的旋转和颜色调整来保持树的平衡,从而保证了在最坏情况下的查找、插入和删除操作的时间复杂度都是 O(log n),其中 n 是树中节点的数量。
红黑树具有以下特性:
红黑树通过旋转操作来维护平衡,主要有左旋和右旋两种操作,它们通过重新排列节点来保持或恢复树的性质。插入和删除操作可能会导致颜色违规或者黑高度违规,因此在执行这些操作后,红黑树需要通过颜色调整和旋转来恢复平衡。
红黑树(Red-Black Tree)和AVL树(Adelson-Velsky and Landis Tree)都是自平衡二叉搜索树,用于在动态数据集上进行高效的插入、删除和搜索操作。它们之间有一些相似之处,但也存在一些关键的区别。下面是红黑树和AVL树的比较:
平衡性:
插入和删除操作的性能:
搜索性能:
存储空间:
适用场景:
总的来说,红黑树和AVL树都有各自的优势和适用场景。选择哪种树结构取决于应用的需求和对性能的具体要求。在实际使用中,还要考虑到具体的操作频率、数据量以及对内存占用的限制。
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
我们分析一下:
最短路径为全黑,最长路径就是红黑节点交替(因为红色节点不能连续),每条路径的黑色节点相同,则最长路径、刚好是最短路径的两倍。
红黑树节点的定义
// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template
struct RBTreeNode
{
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _color(color)
{}
RBTreeNode* _pLeft; // 节点的左孩子
RBTreeNode* _pRight; // 节点的右孩子
RBTreeNode* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
ValueType _data; // 节点的值域
Color _color; // 节点的颜色
};
思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?
插入红色节点树的性质可能不会改变,而插入黑色节点每次都会违反性质4.
通过性质发现: 将节点设置为红色在插入时对红黑树造成的影响是小的,而黑色是最大的
总结:将红黑树的节点默认颜色设置为红色,是为尽可能减少在插入新节点对红黑树造成的影响。
为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为根节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1,按照二叉搜索的树规则插入新节点
bool Insert(const T& value)
{
// 1. 按照二叉搜索树的规则插入新节点
// 空树
Node*& root = GetRoot();
if (nullptr == root)
{
root = new Node(value, BLACK);
root->_parent = _head;
}
else
{
// 非空
// 按照二叉搜索树的特性找待插入节点在树中的位置
Node* cur = root;
Node* parent = _head;
while (cur)
{
parent = cur;
if (value < cur->_value)
cur = cur->_left;
else if (value > cur->_value)
cur = cur->_right;
else
return false;
}
// 插入新节点
cur = new Node(value);
if (value < parent->_value)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
// 2. 检测新节点插入之后是否违反性质三:即是否存在红色节点连在一起的情况
// 因为新插入节点cur的颜色是红色的,如果cur双亲parent节点的颜色也是红色的
// 则违反了性质三
while (RED == parent->_color) // ???
{
// 违反了性质三
Node* grandFather = parent->_parent;
// 此处grandFather一定不为空
// 因为:parent是红色的,则parent一定不是根节点,parent的双亲一定是存在的
if (parent == grandFather->_left)
{
// 课件中给的三种情况
Node* uncle = grandFather->_right;
if (uncle && RED == uncle->_color)
{
// 情况一:叔叔节点存在且为红
parent->_color = BLACK;
uncle->_color = BLACK;
grandFather->_color = RED;
cur = grandFather;
parent = cur->_parent;
}
else
{
// 叔叔节点为空 || 叔叔节点存在且为黑--->即情况二 或者 情况三
// 情况三
if (cur == parent->_right)
{
// 先对parent进行左单旋,然后将parent和cur交换---->变成情况二
RotateLeft(parent);
swap(parent, cur);
}
// 情况二:
// 将祖父和双亲节点的颜色交换,然后再对祖父树进行右单旋
grandFather->_color = RED;
parent->_color = BLACK;
RotateRight(grandFather);
}
}
else
{
// 课件总给的三种情况的反情况
}
}
}
// 需要更新_head的left和right指针域
_head->_left = MostLeft();
_head->_right = MostRight();
root->_color = BLACK;
return true;
}
2, 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
说明:u的情况有两种
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4: 每条路径黑色节点个数相同。
2.如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。
针对每种情况进行相应的处理即可。
bool Insert(const ValueType& data)
{
// ...
// 新节点插入后,如果其双亲节点的颜色为空色,则违反性质3:不能有连在一起的红色结
点
while(pParent && RED == pParent->_color)
{
// 注意:grandFather一定存在以升序(降序)插入构建红黑树
// 因为pParent存在,且不是黑色节点,则pParent一定不是根,则其一定有双亲
PNode grandFather = pParent->_pParent;
// 先讨论左侧情况
if(pParent == grandFather->_pLeft)
{
PNode unclue = grandFather->_pRight;
// 情况三:叔叔节点存在,且为红
if(unclue && RED == unclue->_color)
{
pParent->_color = BLACK;
unclue->_color = BLACK;
grandFather->_color = RED;
pCur = grandFather;
pParent = pCur->_pParent;
}
else
{
// 情况五:叔叔节点不存在,或者叔叔节点存在且为黑
if(pCur == pParent->_pRight)
{
_RotateLeft(pParent);
swap(pParent, pCur);
}
// 情况五最后转化成情况四
grandFather->_color = RED;
pParent->_color = BLACK;
_RotateRight(grandFather);
}
}
else
{
// 右侧请学生们自己动手完成
}
}
// ...
}
红黑树的检测分为两步:
bool IsValidRBTree()
{
PNode pRoot = GetRoot();
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (BLACK != pRoot->_color)
{
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
PNode pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->_color)
blackCount++;
pCur = pCur->_pLeft;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(PNode 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->_color)
k++;
// 检测当前节点与其双亲是否都为红色
PNode pParent = pRoot->_pParent;
if (pParent && RED == pParent->_color && RED == pRoot->_color)
{
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&
_IsValidRBTree(pRoot->_pRight, k, blackCount);
}