赤黒い木
1 はじめに
- この記事は、C++ を理解するために、赤黒ツリーと色の変化の回転ルールの基本概念を理解することを目的としています。 a> の基本原則は red- の削除操作をカバーしていません。黒い木々。 set と map
- 基本的な回転操作 (単一回転と二重回転) については、この記事では詳しく説明しません。詳細な説明はここにあります:
AVL ツリーの回転の説明 。
2. 赤黒木の簡単な説明
2.1 コンセプト
赤黒ツリーは二分探索ツリーですが、各ノードにはノードの色を表すストレージ ビットが追加されます 、赤 または 黒です。赤黒ツリーは、ルートからリーフまでのパス上の各ノードの色を制限することで、最長パスが最短パスの 2 倍を超えないようにします なので、平衡に近い状態になります。
2.2 プロパティ
- 各ノードは赤または黒です。
- ルート ノードは黒です。 (回転数を減らすため、回転については後で理解します)
- 赤いノードの場合、その子は黒のみになります。 (つまり、パス上に連続した赤いノードが表示されることはありません)
- 各パスには同じ数の黒いノードが含まれている必要があります。
上記のルールの制限により、赤黒ツリーの最長パスは最短パスの 2 倍を超えることはありません。 、高度な相対的バランスを維持。
3 と 4 を組み合わせて、次の 2 つのパスを確認します。
最長: 黒、赤、黒、赤、黒、赤... a>
最短: 黒、黒、黒…………
3. 赤黒木の挿入
3.1 新規挿入ノードの色について
新しく挿入したノードについては、赤に設定します。その理由は、赤黒ツリーの各パスには同じ数の黒いノードが含まれている必要があるためです (プロパティ 4) 、新しく挿入された赤いノードは、赤黒ツリーの構造を間違いなく破壊します。新しく挿入された黒いノードは、プロパティ 4 に間違いなく準拠しません 調整が難しくなります。
3.2 ノードの定義
//用枚举来定义颜色
enum Color
{
RED,
BLACK
};
//这里直接实现key_value模型
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent; //涉及到旋转,多加父亲指针来简化操作
pair<K, V> _kv; //存储键值对
Color _col; //颜色
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_col(RED) //新节点颜色为红色
{
}
};
3.3 新しいノードの挿入
これは比較的簡単です。二分探索ツリーのルールに従って挿入するだけです。
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
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);
if (kv.first > parent->_kv.first) //新节点在父亲右子树
{
parent->_right = cur;
}
else //新节点在父亲左子树
{
parent->_left = cur;
}
cur->_parent = parent; //记得更新父亲指针
/// 变色旋转维持红黑树结构(暂时省略) //
_root->_col = BLACK; //可能改变根部颜色,保持根部为黑色
return true;
}
3.4 挿入後に調整が必要かどうかを判断する
実際、赤黒ツリーを挿入した後は、現在のノードと親の色を確認するだけでよく、新しいノードは赤でなければなりません。
- 父親は黒人であり、ルールに準拠しており、調整の必要はありません。
- 父親は赤色ですが、このとき赤色の連続ノードが現れるので調整が必要です。
3.5 挿入後の赤黒ツリー構造の維持(キーポイント)
説明の便宜上、次のように定義します。
- cur は現在のノードを表します
- p は cur の親ノードを表します
- u は叔父ノードを表します
- g は祖父 (p と u の父) ノードを表します
3.5.1cur、p、u は赤、g は黒
コード:
while (parent && parent->_col == RED) //父亲为红就调整,调整到根部要结束
{
Node* granderfather = parent->_parent; //祖父
//需要对叔叔进行操作,需要判断叔叔是祖父的左还是右
if (parent == granderfather->_left) //父亲是祖父的左子树
{
Node* uncle = granderfather->_right;
if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
{
uncle->_col = parent->_col = BLACK;
granderfather->_col = RED;
//当前子树可能为部分,继续向上调整
cur = granderfather;
parent = cur->_parent;
}
else //叔叔为空或为黑色
{
//先省略
}
}
else //父亲是祖父的右子树
{
Node* uncle = granderfather->_left;
if (uncle && uncle->_col == RED) //叔叔不空并且为红
{
parent->_col = uncle->_col = BLACK;
granderfather->_col = RED;
//当前可能为部分子树,需要继续上调
cur = granderfather;
parent = cur->_parent;
}
else //叔叔为空或为黑色
{
// 先省略
}
}
}
3.5.2cur、p は赤、g は黒、u は空 / u が存在する場合は黒
後で使用する回転インターフェイスは次のとおりです。
void RotateL(Node* parent) //左单旋,rotate->旋转
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left; //这个有可能为空
Node* ppnode = parent->_parent; //原来父亲的父亲
parent->_right = SubRL;
if (SubRL) SubRL->_parent = parent;
SubR->_left = parent;
parent->_parent = SubR;
if (ppnode == nullptr) //旋转的是整颗树
{
_root = SubR;
SubR->_parent = nullptr;
}
else //旋转的是部分
{
if (ppnode->_left == parent) //是左子树
{
ppnode->_left = SubR;
}
else //是右子树
{
ppnode->_right = SubR;
}
SubR->_parent = ppnode;
}
}
void RotateR(Node* parent) //右单旋细节处理和左单旋差不多
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right; //这个有可能为空
Node* ppnode = parent->_parent;
parent->_left = SubLR;
if (SubLR) SubLR->_parent = parent;
SubL->_right = parent;
parent->_parent = SubL;
if (ppnode == nullptr) //旋转的是整颗树
{
_root = SubL;
SubL->_parent = nullptr;
}
else //旋转部分
{
if (ppnode->_left == parent) //是左子树
{
ppnode->_left = SubL;
}
else //右子树
{
ppnode->_right = SubL;
}
SubL->_parent = ppnode;
}
}
回転を伴う状況はより複雑なので、別途説明します。
(1)p は g の左の子、cur は p の左の子です
(2)p は g の左の子、cur は p の右の子です
(3)p は g の右側の子、cur は p の右側の子です
(4)p は g の右の子、cur は p の左の子です
(1、2、3、4) を積分して、次の調整コードを取得します。
//到这里插入新节点的工作完成,下面进行结构调整:
while (parent && parent->_col == RED) //父亲为红就调整,调整到根部要结束
{
Node* granderfather = parent->_parent; //祖父
if (parent == granderfather->_left) //父亲是祖父的左子树,p为g的左孩子
{
Node* uncle = granderfather->_right;
if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
{
uncle->_col = parent->_col = BLACK;
granderfather->_col = RED;
//当前子树可能为部分,继续向上调整
cur = granderfather;
parent = cur->_parent;
}
else //叔叔为空或为黑色
{
// g
// p u
// c
if (cur == parent->_left) //当前为父亲的左子树,cur为p的左孩子
{
RotateR(granderfather);
granderfather->_col = RED;
parent->_col = BLACK;
}
else //当前为父亲的右子树,cur为p的右孩子
{
// g
// p u
// c
//左右双旋
RotateL(parent);
RotateR(granderfather);
granderfather->_col = RED;
cur->_col = BLACK;
}
break; //这两种情况调整完可以结束
}
}
else //父亲是祖父的右子树,p为g的右孩子
{
Node* uncle = granderfather->_left;
if (uncle && uncle->_col == RED) //叔叔不空并且为红
{
parent->_col = uncle->_col = BLACK;
granderfather->_col = RED;
//当前可能为部分子树,需要继续上调
cur = granderfather;
parent = cur->_parent;
}
else //叔叔为空或为黑色
{
if (cur == parent->_right) //当前为父亲的右,cur为p的右孩子
{
// g
// u p
// c
//左旋
RotateL(granderfather);
parent->_col = BLACK;
granderfather->_col = RED;
}
else //当前为父亲的左,cur为p的左孩子
{
// g
// u p
// c
//右左双旋
RotateR(parent);
RotateL(granderfather);
cur->_col = BLACK;
granderfather->_col = RED;
}
break; //这两种情况调整完可以结束
}
}
}
_root->_col = BLACK; //保持根部为黑色
4. いくつかの簡単なテストインターフェイス
void InOrder() //中序遍历,验证是否为二叉搜索树
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
// 根节点->当前节点这条路径的黑色节点的数量
bool Check(Node* root, int blacknum, const int refVal)
{
if (root == nullptr) //到根部看看当前路径黑色节点和标准值是否一致
{
//cout << balcknum << endl;
if (blacknum != refVal)
{
cout << "存在黑色节点数量不相等的路径" << endl;
return false;
}
return true;
}
/检查子比较复杂,可以反过来去检查红节点父是否为黑色
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
++blacknum; //为黑节点加一
}
return Check(root->_left, blacknum, refVal)
&& Check(root->_right, blacknum, refVal);
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
//参考值,即先算出一条路径的黑色节点数
int refVal = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refVal;
}
cur = cur->_left;
}
int blacknum = 0;
return Check(_root, blacknum, refVal);
}
5. 完全なコード
#pragma once
#include <iostream>
#include <utility>
using namespace std;
//用枚举来定义颜色
enum Color
{
RED,
BLACK
};
//这里直接实现key_value模型
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent; //涉及到旋转,多加父亲指针来简化操作
pair<K, V> _kv; //存储键值对
Color _col; //颜色
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_col(RED) //新节点颜色为红色
{
}
};
template<class K, class V>
class RBTree
{
public:
typedef RBTreeNode<K, V> Node;
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
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);
if (kv.first > parent->_kv.first) //在右子树
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED) //父亲为红就调整,调整到根部要结束
{
Node* granderfather = parent->_parent; //祖父
if (parent == granderfather->_left) //父亲是祖父的左子树
{
Node* uncle = granderfather->_right;
if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
{
uncle->_col = parent->_col = BLACK;
granderfather->_col = RED;
//当前子树可能为部分,继续向上调整
cur = granderfather;
parent = cur->_parent;
}
else //叔叔为空或为黑色
{
// g
// p u
// c
if (cur == parent->_left) //当前为父亲的左子树
{
RotateR(granderfather);
granderfather->_col = RED;
parent->_col = BLACK;
}
else //当前为父亲的右子树
{
// g
// p u
// c
//左右双旋
RotateL(parent);
RotateR(granderfather);
granderfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
else //父亲是祖父的右子树
{
Node* uncle = granderfather->_left;
if (uncle && uncle->_col == RED) //叔叔不空并且为红
{
parent->_col = uncle->_col = BLACK;
granderfather->_col = RED;
//当前可能为部分子树,需要继续上调
cur = granderfather;
parent = cur->_parent;
}
else //叔叔为空或为黑色
{
if (cur == parent->_right) //当前为父亲的右
{
// g
// u p
// c
//左旋
RotateL(granderfather);
parent->_col = BLACK;
granderfather->_col = RED;
}
else //当前为父亲的左
{
// g
// u p
// c
//右左双旋
RotateR(parent);
RotateL(granderfather);
cur->_col = BLACK;
granderfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK; //保持根部为黑色
return true;
}
/// //
/// /
/// 测试代码
void InOrder() //中序遍历,验证是否为二叉搜索树
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
// 根节点->当前节点这条路径的黑色节点的数量
bool Check(Node* root, int blacknum, const int refVal)
{
if (root == nullptr) //到根部看看当前路径黑色节点和标准值是否一致
{
//cout << balcknum << endl;
if (blacknum != refVal)
{
cout << "存在黑色节点数量不相等的路径" << endl;
return false;
}
return true;
}
/检查子比较复杂,可以反过来去检查红节点父是否为黑色
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
++blacknum; //为黑节点加一
}
return Check(root->_left, blacknum, refVal)
&& Check(root->_right, blacknum, refVal);
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
//参考值,即先算出一条路径的黑色节点数
int refVal = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refVal;
}
cur = cur->_left;
}
int blacknum = 0;
return Check(_root, blacknum, refVal);
}
int Height()
{
return _Height(_root);
}
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;
}
Node* Find(K key)
{
return _Find(key, _root);
}
Node* _Find(K key, Node* root)
{
if (root == nullptr)
return nullptr;
if (key > root->_kv.first) //在右子树
{
return _Find(key, root->_right);
}
else if (key < root->_kv.first) //在左子树
{
return _Find(key, root->_left);
}
else //找到了
{
return root;
}
}
private:
Node* _root = nullptr;
void RotateL(Node* parent) //左单旋,rotate->旋转
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left; //这个有可能为空
Node* ppnode = parent->_parent; //原来父亲的父亲
parent->_right = SubRL;
if (SubRL) SubRL->_parent = parent;
SubR->_left = parent;
parent->_parent = SubR;
if (ppnode == nullptr) //旋转的是整颗树
{
_root = SubR;
SubR->_parent = nullptr;
}
else //旋转的是部分
{
if (ppnode->_left == parent) //是左子树
{
ppnode->_left = SubR;
}
else //是右子树
{
ppnode->_right = SubR;
}
SubR->_parent = ppnode;
}
}
void RotateR(Node* parent) //右单旋细节处理和左单旋差不多
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right; //这个有可能为空
Node* ppnode = parent->_parent;
parent->_left = SubLR;
if (SubLR) SubLR->_parent = parent;
SubL->_right = parent;
parent->_parent = SubL;
if (ppnode == nullptr) //旋转的是整颗树
{
_root = SubL;
SubL->_parent = nullptr;
}
else //旋转部分
{
if (ppnode->_left == parent) //是左子树
{
ppnode->_left = SubL;
}
else //右子树
{
ppnode->_right = SubL;
}
SubL->_parent = ppnode;
}
}
};