赤黒い木
コンセプト
赤黒木は二分探索木ですが、ノードの色 (赤または黒) を示すために各ノードにストレージ ビットが追加されます。ルートからリーフまでのいずれか 1 つのパスの各ノードの色付けを制限することにより、赤黒ツリーは、他のパスの 2 倍の長さのパスがないことを保証し、バランスに近い(厳密にバランスが取れていない) ようにします。
自然
プロパティ 3 と 4 が最も重要であり、プロパティ 5 は無視できます
- 各ノードは、赤または黒のいずれかです。
- ルート ノードは黒です。
- 連続する赤のノードはなく、ノードが赤の場合、その 2 つの子は black です。ただし、ノードは黒であり、その子は黒にすることができます。
- ノードごとに、ノードからそのすべての子孫リーフ ノードへの単純なパスには、同じ数の黒いノードが含まれます。
- 各 NIL ノード/葉ノード/空のノードは黒で、葉ノード == null ノード== NIL ノードです。
上記のプロパティを満たす赤黒木は、最長パスのノード数が最短パスのノード数の 2 倍を超えないことを保証できます。
Shortest path : 極端な場合、すべて黒のパス、またはパスが通過する黒のノードの数です。
最長パス: 1 つの黒と 1 つの赤のパス。
赤黒木がバランスに近いという概念について話し合う
最悪の場合:左右のバランスが極端に悪い場合、つまりサブツリーが黒と赤の場合が最悪2 log 2 N 2log_2N2ログ_ _2N、他のサブツリーはすべて黒ですlog 2 N log_2Nログ_ _2ん。
最適な状況: 左右のバランスが最適です。つまり、すべてが黒であるか、各パスが黒と赤で、木が完全な二分木に近い場合です。
コード
ノード定義
ノードの定義では、ノードのデフォルトの色は赤です。
理由: すべての新しいノードが黒の場合、規則 4 (すべてのパスの黒ノードの数が同じであるという規則) に違反している必要があり、新しいノードが赤であるたびに、そのノードの親ノードが必ずしも赤であるとは限りません。 、黒かもしれないのでルール3(赤いノードが連続しない)に違反している可能性があるので、デフォルトの色は赤です。
enum Color { RED, BLACK };
template<class K, class V>
struct RBTreeNode
{
RBTreeNode(const pair<K, V>& kv, Color color = RED)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _color(color)
{}
RBTreeNode<K, V>* _left; // 节点的左孩子
RBTreeNode<K, V>* _right; // 节点的右孩子
RBTreeNode<K, V>* _parent;// 节点的双亲
KV _kv; // 节点的值
Color _color; // 节点的默认颜色给成红色的
};
入れる
二分探索木法に従って新しいノードを挿入し、新しいノードの挿入後に赤黒木の性質が損なわれているかどうかを確認し、ルールが満たされている場合は直接終了します。ルールが満たされていない場合は、ノードの色を更新する必要があります。赤黒木は、赤黒木に関する規則の要件を満たすように回転され、色付けされます。
規則: cur は現在のノード、p は親ノード、g は祖父母ノード、u は叔父ノードです。
//父节点
parent = cur->_parent;
//祖父节点
grandparent = parent->_parent;
//叔叔节点
if(grandparent->left == parent)
uncle = grandparent->right;
else
uncle = grandparent->left;
変色3例
ケース 1: p は赤、g は黒、cur は赤、u は存在し、赤です。[p は赤、g は黒、cur は赤 ==> while ループは親が存在し、親の色が赤であると判断します。まず、親が赤の場合、祖父母が存在し、黒でなければならないことを意味します (ルール 2 + ルール 3); 次に、新しく追加されたノードの色はデフォルトで赤です]
解決:
- p、u を黒に、g を赤に変更します。
- 次に、g と cur を更新し、cur に元の g を与え、g を cur の親に与え、上方調整を続けます。
ケース 2: cur、p、および g が直線上にある (2.1 p は g の左の子、cur は p の左の子、または 2.2 p は g の右の子、cur は p の右の子)、 p は赤、g は黒、cur は赤、u は存在しない、または u は存在するが黒です。[p は赤、g は黒、cur は赤 ==> while ループは親が存在し、親の色が赤であると判断します。まず、親が赤の場合、それは祖父母が存在し、黒でなければならないことを意味します (ルール 2 + ルール 3); 次に、新しく追加されたノードのデフォルトの色を赤にします。u が存在しないか、u が存在して黒である ==> while ループ内で判断する]
解決:
- 2.1 p が g の左の子で、cur が p の左の子である場合、g ノードで右に 1 回回転すると、p が黒になり、g が赤になります。
- 2.2 p が g の右の子で、cur が p の右の子である場合、g ノードで左に 1 回回転すると、p が黒になり、g が赤になります。
- 次に、g と cur を更新し、cur に元の g を与え、g を cur の親に与え、上方調整を続けます。
ケース 3: cur、p、および g が破線上にある (3.1 p は g の右の子、cur は p の左の子、または 3.2 p は g の左の子、cur は p の右の子)、 p は赤、g は黒、cur は赤、u は存在しない、または u は存在するが黒です。[p は赤、g は黒、cur は赤 ==> while ループは親が存在し、親の色が赤であると判断します。まず、親が赤の場合、それは祖父母が存在し、黒でなければならないことを意味します (ルール 2 + ルール 3); 次に、新しく追加されたノードのデフォルトの色を赤にします。u が存在しないか、u が存在して黒である ==> while ループ内で判断する]
解決:
- 3.1 p が g の左の子で、cur が p の右の子である場合、p ノードで左に 1 回回転し、g ノードで右に 1 回回転すると、cur が黒になり、g が赤になります。
- 3.2 p が g の右の子で、cur が p の左の子である場合、p ノードで右に 1 回回転し、g ノードで左に 1 回回転すると、cur が黒になり、g が赤になります。
- 次に、g と cur を更新し、cur に元の g を与え、g を cur の親に与え、上方調整を続けます。
bool Insert(const std::pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_color = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
// 1、找到合适的插入点
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;
}
}
// 2、开始插入以及更新节点颜色
cur = new Node(kv);
cur->_color = RED;//新插入的节点给红色
//比父节点的值大,就插在右边
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
//比父节点的值小,就插在左边
else
{
parent->_left = cur;
cur->_parent = parent;
}
// 3、插入成功,更新节点颜色
// 3.1 若父亲颜色为黑色,结束退出
// 3.2 若父亲颜色为红色,开始调整颜色
while(parent && RED == parent->_color)
//这个循环条件保证了grandparent一定存在,因为parent是红色,而根节点必须为黑色
// 因为parent存在,且不是黑色节点,则parent一定不是根,则其一定有双亲
{
Node* grandparent = parent->_parent;
//情况1和x.1
if (grandparent->_left == parent)
{
Node* uncle = grandparent->_right;
// 情况1: p为红,g为黑,cur为红,u存在且为红
if (uncle && uncle->_color == RED)
{
//将p、u改为黑,g改为红
parent->_color = BLACK;
uncle->_color = BLACK;
grandparent->_color = RED;
//更新cur节点
cur = grandparent;
parent = cur->_parent;
}
//情况2、情况3
else
{
//情况2.1
if (cur == parent->_left)
{
RotateR(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
}
//情况3.1
else
{
RotateL(parent);
RotateR(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
}
break;
}
}
情况1和x.2
else
{
Node* uncle = grandparent->_left;
// 情况1: p为红,g为黑,cur为红,u存在且为红
if (uncle && uncle->_color == RED)
{
//将p、u改为黑,g改为红
parent->_color = BLACK;
uncle->_color = BLACK;
grandparent->_color = RED;
//更新cur节点
cur = grandparent;
parent = cur->_parent;
}
//情况2、情况3
else
{
//情况2.2
if (cur == parent->_right)
{
RotateL(grandparent);
parent->_color = BLACK;
grandparent->_color = RED;
}
//情况3.2
else
{
RotateR(parent);
RotateL(grandparent);
cur->_color = BLACK;
grandparent->_color = RED;
}
break;
}
}
}
_root->_color = BLACK;//在修改grandparent的颜色时,有可能改掉了根的颜色,再循环外要改为黑色
return true;
}
パフォーマンス
時間の複雑さだけでも、AVL ツリーは赤黒ツリーよりも優れています。ただし、AVL ツリーはローテーションによってバランスを調整することで多くのパフォーマンスを消費しますが、赤黒ツリーは厳密なバランスを必要としないため、ローテーションによるパフォーマンスの消費が抑えられます。