本章代码gitee仓库:红黑树
文章目录
- 1. 红黑树概念
- 2.红黑树的实现
- 2.1 红黑树结构
- 2.2 插入
- 情况1:`cur`为红,`parent`为红,`grandfather`为黑,`uncle`存在且为红
- 情况2:`cur`为红,`parent`为红,`grandfather`为黑,`uncle`不存在/存在且为黑
- 旋转操作
- 2.3 删除
- 2.4 树的高度
- 2.5 是否为红黑树
- 2.6 红黑树遍历
红黑树(red-black-tree)也是一种二叉搜索树,每个节点上增添了一个存储位表示节点颜色(Red or Black)。通过每条路径上根到叶子的各节点颜色限制,确保最长路径不超过最短路径二倍,达到近似平衡(平衡的限制规则没有AVL树那么严格)。
红黑树性质:
enum Colour
{
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;
Colour _col;
RBTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(key)
, _col(RED)
{}
};
template<class K,class V>
struct RBTree
{
typedef RBTreeNode Node;
public:
//接口
private:
Node* _root;
};
红黑树的插入节点,默认是红色的
Tips:
如果插入节点默认为黑色,那么这颗树整体就被影响了,该路径上的黑色节点与其他路径黑色节点数量不同
如果插入节点默认为红色,那么就算要影响,也是只影响该路径,其他路径不受影响
我们这里设当前节点为cur
,父亲节点为parent
,父亲的兄弟节点为uncle
如果
cur
为黑,就不用管了,所有只当cur
为红的时候,才考虑调整红黑树
cur
为红,parent
为红,grandfather
为黑,uncle
存在且为红解决方式:
将p
、u
改为黑,g改为红,然后把g
当为cur
,继续向上调整
cur
为红,parent
为红,grandfather
为黑,uncle
不存在/存在且为黑
uncle
情况分为2种:
uncle
不存在如果
uncle
节点不存在,那么cur
必定是插入节点,如果cur
不是插入节点,那么cur
和parent
一定有一个是黑色节点,这样就不符合每条路径黑色节点个数相同这个规则
uncle
存在且为黑如果
uncle
为黑,那么cur
原先也一定为黑,下图cur
为红色是cur
子树调整过程中将cur
颜色修改为红色
解决方式:
parent
为grandfather
的左孩子,右旋;
parent
为grandfather
的右孩子,左旋
parent
变黑,grandfather
变红
如果是双旋,
cur
变黑,grandfather
这次情况下,都是要变红
代码实现:
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
//链接
if (parent->_kv.first > cur->_kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//变色
while (parent && parent->_col == RED) //父亲存在且为红就要变色
{
Node* grandfather = parent->_parent; //祖父节点
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//情况1:uncle存在且为红
if (uncle && uncle->_col == RED)
{
//变色
//父亲、叔叔变黑
parent->_col = uncle->_col = BLACK;
//祖父变红
grandfather->_col = RED;
//继续向上调整
cur = grandfather;
parent = cur->_parent;
}
else //叔叔为黑 or 叔叔不存在
{
if (cur == parent->_left)
{
// g p
// / / \
// p ==》 c g
// /
// c
//右旋
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g g c
// / / / \
// p ==》 c ==》 p g
// \ /
// c p
//左右双旋
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else //parent == grandfather->_right
{
Node* uncle = grandfather->_left;
//情况1:同上
if (uncle && uncle->_col == RED) //叔叔存在且为红
{
//叔叔父亲变黑
parent->_col = uncle->_col = BLACK;
//祖父变红
grandfather->_col = RED;
//继续向上调整
cur = grandfather;
parent = cur->_parent;
}
else //叔叔不存在 or 存在且为黑
{
if (cur == parent->_right)
{
// g p
// \ / \
// p ==》 g c
// \
// c
//左旋
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
// g g c
// \ \ / \
// p ==》 c ==》 g p
// / \
// c p
//右左双旋
grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
//左旋
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curLeft = cur->_left;
parent->_right = curLeft;
if (curLeft)
{
curLeft->_parent = parent;
}
cur->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
//右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curRight = cur->_right;
parent->_left = cur->_right;
//防止curRight为空
if (curRight)
{
curRight->_parent = parent;
}
cur->_right = parent;
//保存父亲的父亲节点
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr) //父亲的父亲为空,则为根节点
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
参考《数据结构——用面向对象方法与C++语言描述》
代码实现:
这个是gpt写的,还是有bug
GitHub上面平衡树类的实现:BalanceTree
现在不啃了,沉淀沉淀再来看看,打个标记先2023/9/13
bool _Erase(const pair<K, V>& kv)
{
Node* parent = nullptr; // 父节点指针
Node* cur = _root; // 当前节点指针
Node* double_black_node = nullptr; // 用于处理双黑节点的指针
// 先查找要删除的节点
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left; // 向左子树移动
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right; // 向右子树移动
}
else
break; // 找到要删除的节点
}
// 元素不存在
if (cur == nullptr)
return false;
// 删除节点为叶子或者只有一个子节点
Node* u = nullptr;
if (cur->_left)
{
u = cur->_left;
}
else if (cur->_right)
{
u = cur->_right;
}
// 删除节点为叶子或者只有一个子节点
if (cur->_col == RED) // 删除的节点为红色,直接删除
{
if (parent)
{
if (parent->_left == cur)
{
parent->_left = u;
}
else
{
parent->_right = u;
}
}
else
{
// 如果删除的是根节点
_root = u;
if (_root)
_root->_col = BLACK;
}
// 删除
delete cur;
cur = nullptr;
return true;
}
// 删除节点为黑,进行调整
if (parent)
{
if (parent->_left == cur)
{
parent->_left = u;
}
else
{
parent->_right = u;
}
}
else
{
// 如果删除的是根节点
_root = u;
if (_root)
_root->_col = BLACK;
}
// 删除
delete cur;
cur = nullptr;
// 处理双黑节点
Node* sibling;
while (u != _root && (!u || u->_col == BLACK))
{
if (parent->_left == u)
{
sibling = parent->_right;
if (sibling->_col == RED)
{
// 情况1:兄弟节点为红色
sibling->_col = BLACK;
parent->_col = RED;
RotateL(parent);
sibling = parent->_right;
}
if ((!sibling->_left || sibling->_left->_col == BLACK) && (!sibling->_right || sibling->_right->_col == BLACK))
{
// 情况2:兄弟节点及其子节点都为黑色
sibling->_col = RED;
u = parent;
parent = u->_parent;
}
else
{
if (!sibling->_right || sibling->_right->_col == BLACK)
{
// 情况3:兄弟节点的右子节点为黑色
sibling->_left->_col = BLACK;
sibling->_col = RED;
RotateR(sibling);
sibling = parent->_right;
}
// 情况4:兄弟节点的右子节点为红色
sibling->_col = parent->_col;
parent->_col = BLACK;
sibling->_right->_col = BLACK;
RotateL(parent);
u = _root;
}
}
else
{
sibling = parent->_left;
if (sibling->_col == RED)
{
// 情况1:兄弟节点为红色
sibling->_col = BLACK;
parent->_col = RED;
RotateR(parent);
sibling = parent->_left;
}
if ((!sibling->_left || sibling->_left->_col == BLACK) && (!sibling->_right || sibling->_right->_col == BLACK))
{
// 情况2:兄弟节点及其子节点都为黑色
sibling->_col = RED;
u = parent;
parent = u->_parent;
}
else
{
if (!sibling->_left || sibling->_left->_col == BLACK)
{
// 情况3:兄弟节点的左子节点为黑色
sibling->_right->_col = BLACK;
sibling->_col = RED;
RotateL(sibling);
sibling = parent->_left;
}
// 情况4:兄弟节点的左子节点为红色
sibling->_col = parent->_col;
parent->_col = BLACK;
sibling->_left->_col = BLACK;
RotateR(parent);
u = _root;
}
}
}
if (u)
u->_col = BLACK;
return true;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return max(leftH, rightH) + 1;
}
这里采用顺便一条路径的黑色节点作为基准值,判断每条路径的黑色节点是否相同
bool CheckColour(Node* root, int blackNum, int benchmark)
{
if (root == nullptr)
{
if (blackNum != benchmark)
return false;
return true;
}
if (root->_col == BLACK)
blackNum++;
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
cout << root->_kv.first << "连续红色节点" << endl;
return false;
}
return CheckColour(root->_left, blackNum, benchmark) && CheckColour(root->_right, blackNum, benchmark);
}
//是否为红黑树
bool _IsBanlance(Node* root)
{
if (root == nullptr)
return true;
if (root->_col != BLACK)
return false;
//找容易一条路径的黑色节点个数为基准值 最左为例
int benchmark = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
benchmark++;
cur = cur->_left;
}
return CheckColour(root, 0, benchmark);
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
C++STL
库里面的map
和set
的底层就采用红黑树,大佬写的很棒,我们了解底层之后,直接拿来用即可,学有余力可以啃一啃源码
那么本次分享就到这里,我们下期再见,如果还有下期的话