目录
红黑树的实现的代码:
一、定义与性质
-
定义:红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是红色或黑色。
-
性质:
- 每个结点要么是红色,要么是黑色。
- 根结点是黑色的。
- 所有叶子结点(外部结点、空结点)都是黑色的。
- 每个红色结点的两个子结点都是黑色的(即红色结点不能连续)。
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
-
红黑树的效率
假设N是红黑树树中结点数量,h最短路径的长度,那么2^h - 1 <= N < 2^(2 * h) - 1 由此推出,也就是意味着红黑树增删查改最坏也就是走最长路径 ,那么时间复杂度还是O(log n)。
二、结构特点
- 平衡性:红黑树的这些性质确保了任意路径上的黑色节点数量相等,从而保证了没有一条路径会比其他路径长出两倍,因此红黑树是接近平衡的。这种平衡性使得红黑树在最坏情况下的查找、插入和删除操作的时间复杂度都是O(log n),其中n是树中元素的数目。
- 节点定义:红黑树的每个节点通常包含指向左子节点和右子节点的指针、指向父节点的指针、存储键值对的成员变量、表示节点颜色的枚举类型以及可能的平衡因子(在某些实现中)。
三、红黑树节点的结构
红黑树节点的结构跟平衡二叉树节点的结构很像,都有指向左右子树和父节点的指针,存储数据的对象,不过多了一个枚举类型便于我们平衡红黑树。
cpp
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)
{}
};
四、红黑树的插入
节点着色
- 新节点默认颜色 :红黑树中,新插入的节点默认被着色为红色。这是因为如果新节点被着色为黑色,那么它所在的路径上黑色节点的数量就会增加,这可能会破坏红黑树的平衡性质。而红色节点不会改变路径上黑色节点的数量,只需要通过旋转和变色来调整可能的红色节点连续问题。
- 特殊情况 :如果新插入的节点是根节点,那么它应该被着色为黑色,因为根节点没有父节点,且红黑树要求根节点必须是黑色。
树的平衡调整
当新节点插入后,如果其父节点是黑色,则不会破坏红黑树的平衡性质,无需进行调整。如下:
但如果其父节点是红色,则可能违反红黑树的性质(不能有连续的红色节点),此时需要进行调整。如下:
调整过程涉及以下四种情况:
-
叔叔节点为红色:
- 如果新节点的叔叔节点(即父节点的兄弟节点)也是红色,则将父节点和叔叔节点都着色为黑色,将祖父节点(即父节点的父节点)着色为红色。如果祖父节点是根节点,则将其着色为黑色。如果祖父节点不是根节点,则继续向上检查并调整。
两种情况过程如图所示:(注:gp是祖父,p是父亲,u是叔叔,c是新插入的节点)
- 叔叔节点不存在或为黑色:
如果叔叔节点不存在或为黑色,则需要通过旋转和变色来调整红黑树。旋转包括左旋和右旋两种操作。
- 左旋:对节点x进行左旋,意味着将"x的右孩子变成x的父亲",而将"x原先的右孩子的左孩子变成x的右孩子"。
- 右旋:对节点x进行右旋,意味着将"x的左孩子变成x的父亲",而将"x原先的左孩子的右孩子变成x的右孩子"。
根据新节点与其父节点、祖父节点的相对位置,以及是否需要进行双旋,可以分为以下四种子情况:(注:其中省略了一些节点没有画出)
- RL型 :如果新节点是父节点的左孩子,且父节点是祖父节点的右孩子,则先对父节点进行右旋,使新节点成为其父节点的右孩子,然后再按照RR型进行调整,并且将新插入的节点的颜色改为黑色,祖父的颜色改为红色。(注意,这种情况下的调整过程与LL型和RR型不完全相同,因为旋转的方向和节点的相对位置发生了变化)。
- RR型 :如果新节点是父节点的右孩子,且父节点是祖父节点的右孩子,则对祖父节点进行左旋,并且父亲的颜色改为黑色,祖父的颜色改为红色。(注意,这里的描述与LL型类似,但方向相反)。
- LR型 :如果新节点是父节点的右孩子,且父节点是祖父节点的左孩子,则先对父节点进行左旋,使新节点成为其父节点的左孩子,然后再按照LL型进行调整,并且将新插入的节点的颜色改为黑色,祖父的颜色改为红色。
- LL型 :如果新节点是父节点的左孩子,且父节点是祖父节点的左孩子,则对祖父节点进行右旋,并且父亲的颜色改为黑色,祖父的颜色改为红色。
总而言之
- 当**叔叔节点为红色时:**只需要父亲节点的颜色改为黑色,祖父节点的颜色改为红色。
- 当**叔叔节点不存在或为黑色时:**若需要左右旋转各一次,先判断是先要左旋转还是右旋转,然后将新插入的节点的颜色改为黑色,祖父的颜色改为红色。否则,先左旋转或右旋转,然后将父亲节点的颜色改为黑色,祖父节点的颜色改为红色。
插入代码实现
cpp
bool Insert(const pair<T, V>& kv)
{
if (_pHead == nullptr)
{
_pHead = new Node(kv);
_pHead->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _pHead;
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;
if (parent == grandfather->_left)
{
Node* nucle = grandfather->_right;
if(nucle && nucle->_col == RED)
{
parent->_col = BLACK;
nucle->_col = BLACK;
grandfather->_col = RED;
}
else
{
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else
{
Node* nucle = grandfather->_left;
if (cur == parent->_right)
{
if (nucle && nucle->_col == RED)
{
parent->_col = BLACK;
nucle->_col = BLACK;
grandfather->_col = RED;
}
}
else
{
if(cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_pHead->_col = BLACK;
return true;
}
五、查找红黑树的节点
从根节点开始找,若要查找的值比当前节点的值要大就往右子树找,反之,向左子树里查找,循环这个操作即可,若走到nullptr都没有找到,直接返回空指针。
cpp
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
六、获取红黑树的高度和节点个数
获取红黑树的高度和节点个数的方法与获取普通的二叉树的高度和节点个数没有什么区别,用深度优先遍历即可。
cpp
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
int _Size(Node* root)
{
if (root == nullptr)
return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}