赤黒木 (red-black tree) は 1972 年に Rudolf Bayer によって発明された自己平衡二分木です. 発明された当時は対称. Robert Sedgewick は 1978 年に論文を発表しました。赤黒木の構造は複雑ですが、操作の最悪の場合の実行時間は良好です。時間。
赤黒木は、次の色付けプロパティを持つ BST です。
- 各ノードは黒または赤のいずれかです
- 根元が黒い
- ノードが赤の場合、その子は黒でなければなりません
- ノードから NULL ポインタへのすべてのパスには、同じ数の黒いノードが含まれている必要があります
彩色規則によれば、赤黒のツリーの高さは最大で である ため、ルックアップは対数演算であることが保証されています。もちろん、別の合意があります。空のノード nullptr が黒であると仮定して、合意に違反することなく便利に操作できるようにします。
通常の問題は、新しいノードを挿入した後、ノードを黒くペイントするとプロパティ 4 に違反することです。これは、パス上の黒いノードの数が 1 つ増えるためですが、他のパス上の黒いノードの数は同じままになるためです。
したがって、ノードを挿入する場合、デフォルト ノードは赤で、親ノードが黒の場合は直接挿入されます。以下の場合は、このケースは議論されません。赤の親ノードはルール 3 に違反します。この場合、すべてのプロパティを満たすようにツリーを変更する必要があります。
赤黒木へのボトムアップ挿入
新しく挿入されたノード X が赤の場合、親 P、兄弟 S、叔父 U、および祖父母 G があります。次に、考慮すべき状況がいくつかあります。
- P と U が両方とも赤、つまり G が黒の場合、P、U を黒に、G を赤に再描画できます。これはルール 3 にもルール 4 にも違反しません。しかし、G より上の状況はわかりません。G もルートである可能性があるため、G の上に再帰的に再描画する必要があります。
- P と U の一方だけが赤の場合、G は黒であり、挿入された X はルール 4 には違反していませんが、ルール 3 に違反しているため、X または P のいずれかが強制的に黒になり、ルール 4 に違反しています。ツリーを再度要件を満たすには、ツリーを回転させてノードの色を再描画する必要があります. 実は、ここでの回転操作は AVL ツリーと同じですが、ノードのバランス係数が色情報に変換されます. . a. X、P、G がジグザグを形成する場合、1 回転を使用します b. X、P、G がジグザグを形成する場合、2 回転を使用します
// 该函数仅处理父结点是红色的情况,黑色情况则直接插入即可
// 使用头结点方便处理,头结点的 parent 指向 root,root 的 parent 指向 head
void insert_help(Node* node, Node* head) {
// 当结点不是树的根或父结点是红色时,进行循环
while (node != head->parent && node->parent->tag == RED) {
Node* uncle = get_uncle(node);
Node* grandparent = get_grandparent(node);
Node* parent = node->parent;
// 如果叔父结点不为空且为红色,符合情况 1
if (uncle != nullptr && uncle->tag == RED) {
parent->tag = uncle->tag = BLACK;
grandparent->tag = RED;
node = grandparent;
continue;
}
// 判断 zig-zig 或 zig-zag 类型,进行相应的旋转
if (parent == grandparent->left) {
if (node == parent->right) { // l-r 的 zig-zag
node = parent;
parent = rotate_left(parent);
}
rotate_right(grandparent); // l-l 的 zig-zig
} else {
if (node == node->parent->left) { // r-l 的 zig-zag
node = parent;
parent = rotate_right(parent);
}
ratate_left(grandparent); // r-r 的 zig-zig
}
grandparent->tag = RED;
parent->tag = BLACK;
}
head->parent->tag = BLACK;
}
赤黒木のトップダウン挿入
ボトムアップ操作には親ポインターまたはスタック ストレージ パスが必要ですが、トップダウン操作は実際には S が赤にならないというトップダウン保証を赤黒木に適用するプロセスです。
下向きのプロセスで、ノード N に 2 つの赤い子がある場合、子を黒として再描画し、ノード N を赤として再描画します。ノード N とその親ノード P が両方とも赤の場合、赤黒木の色付けプロパティに違反し、このとき、ジグジグまたはジグザグで回転できます。叔父ノード U については、トップダウン処理で赤の可能性が除外されます。
// 自顶向下插入,value 是待插入的值
void insert(Node* node, Node* head, T& value, Node** pos = nullptr) {
bool inserted = false;
if (node == nullptr) { // 插入结点,默认为红色
node = new Node(value);
*pos = node;
inserted = true;
}
if (node->left != nullptr && node->left->tag == RED &&
node->right != nullptr && node->right->tag == RED) {
node->left->tag = node->right->tag = BLACK;
node->tag = RED;
}
head->parent->tag = BLACK;
Node* gp = get_grandparent(node);
Node* parent = node->parent;
if (node->tag == RED && parent->tag == RED) {
// 判断 zig-zig 或 zig-zag 类型,进行相应的旋转
if (parent == gp->left) {
if (node == parent->right) { // l-r 的 zig-zag
parent = rotate_left(parent);
}
rotate_right(gp); // l-l 的 zig-zig
} else {
if (node == parent->left) { // r-l 的 zig-zag
parent = rotate_right(parent);
}
ratate_left(gp); // r-r 的 zig-zig
}
gp->tag = RED;
parent->tag = BLACK;
}
if (inserted) {
return;
}
if (node->val < value) {
insert(node->left, head, &node->left, value);
} else {
insert(node->right, head, &node->right, value);
}
}
赤黒木のトップダウン削除
ノードを削除する場合、2 つの子を持つノードを削除すると、左側のサブツリーの最大ノードまたは右側のサブツリーの最小ノードの値を交換できるため、すべてのケースがリーフ ノードの削除に起因する可能性があります。色を変えず、赤黒木の性質に影響を与えません。次に、交換された葉ノードを削除します。赤い葉のノードは直接削除できますが、赤黒木の構造には影響しません. 子がある場合は、その子に置き換えるだけで済みます. そのため、トップダウン プロセスでリーフ ノードが赤色であることを確認する必要があります。
現在のノードが N、その兄弟ノード S、親ノード P、叔父ノード U、および祖父母ノード G であるとします。まず、ツリーのルートを赤く塗り直し、ツリーをたどり、ノードに到達したら、P が赤、N と S が黒であることを確認します。途中でいくつかの状況に遭遇します。
- N には 2 つの黒の子があり、この時点で a. S にも 2 つの黒の子があり、N、S、P の色を再描画して逆にすると、ツリー構造は変更されません b. S には赤の子があり、赤の子に従って進みます信号回転または二重回転。両方の子が赤の場合、回転する子を 1 つ選択します
- N には赤い子があり、この時点で下方に再帰します a. 新しい N は赤で、再帰を続行します b. 新しい N は黒で、S と P を回転させ、S が P の親ノードになり、P と P の関係を再描画しますS Color、赤の親ノード P を取得できます。P の場合、ケース 1 に戻る