ノードを挿入し、削除します
私たちは、色と回転を補正するために、赤黒木を破損していることを知っているので、赤、黒の木、新しい均衡に沿ったルール。そして、特定の状況で動作するようにどのようにノードを追加または削除しますか?
挿入ノード
バイナリ検索ツリー挿入手順赤黒木にノードがマルチにおいて同じ、最後のステップであるバランス調整操作。
赤色のノードに新たに挿入されたノードのデフォルト、バランス調整の必要がない、唯一の新しいノードが挿入されてによる赤ノードの下の違反に、黒のノードに挿入された赤色次のノードに挿入されたときに調整する必要が二つの隣接する赤のノードできませんルール。
状況次の挿入ノードのバランス調整:
- 兄弟親ノード(すなわち叔父ノード)赤
- 兄弟親ノードNULLにノード(NULLデフォルトは黒である)、および祖父母、親ノードと一直線に新しいノード
- 兄弟親ノードは、直線NULLノードと祖父母、親ノードと新しいノードではありません
ケース1:
兄弟ノードの親ノードが、カラーバランス調整動作赤色である場合、新たに挿入された親ノードは、赤色です。
黒にこの時点で、親や兄弟、親ノード(ノード叔父)、祖父ノード(親の親の)は、赤い上向きに続く彼の祖父の修理ノードベースの検証の隣に、しかし最終的にはルート必須になります黒。
図:ノードに挿入順次[200,100,300,50]
ケース2:
新しい親ノードを挿入赤、親ノードの兄弟黒であり、親ノード、祖父母ノードと新しいノードと同じ方向(直線)で3つのノード、直線として残す場合(例えば:及び「/」)と同じ方向に、この時間は()祖父母のために右旋性必要が。
行右(のような:と同じ方向に「\」)場合は、この時点のニーズに左。次いで、新しいノードの親ノードは、その兄弟(元の祖父母)が赤色に変わり、黒となります。
図順次[200,100,300,50,20]ノードに挿入された - 右利きの例の場合
ケース3:
ケースは2回転する必要があります。
新插入节点的父节点为红色,父节点的兄弟节点为黑色时,且父节点、祖父节点和新节点3个节点不在同一方向上(不是一条直线),如果新节点是父节点的右子节点(此时,父节点在祖父节点的左侧,此时这三个节点构成这样“<”方向非直线),此时需要先左旋(变为一条直线)再右旋,两次旋转。
如果新节点是父节点的左子节点(此时,父节点在祖父节点的右侧,此时这三个节点构成这样“>”方向非直线),此时需要先右旋(变为一条直线)再左旋,两次旋转。
可以发现,经过第一次旋转后,其实是被转变成了Case 2 的情况。
如图: 依次插入节点[200,100,300,50,80] - 以先左再右旋为例
伪代码- 插入元素平衡修正(来源Java TreeMap):
void fixAfterInsertion(Node<K,V> x) {
// 新插入节点默认红色
x.color = RED;
// 不是根节点且父节点为红色
while (x != null && x != root && x.parent.color == RED) {
//父节点在祖父节点的左侧
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// 叔叔节点
Node<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
// 父节点设为黑色
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
// 祖父节点设为红色
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
// 左旋
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
// 右旋
rotateRight(parentOf(parentOf(x)));
}
} else { // 对称的
Node<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
删除节点
红黑树的节点删除和二叉搜索树的节点删除过程是一样的,只是最后多了一步平衡纠正操作。
只有当删除黑色节点时需要平衡调整(删除红色节点时不需要平衡调整,没破坏红黑树规则),因删除黑色节点后,会违反从任意节点到叶子节点的每条路径包含的黑色节点数目相同的规则。
删除节点要比插入节点复杂些,插入节点整个过程是先找到插入节点位置 -> 插入节点 -> 平衡调整。而删除节点整个过程是先找到删除节点的位置 -> 节点的位置变换 -> 删除节点 -> 平衡调整,且调整的情况也相对较多。
前面说过,红黑树与二叉搜索树的删除节点过程一样,当删除节点存在两个子节点,会被转换为删除有一个子节点或不存在子节点的节点。
对于有一个子节点的节点删除,比较简单,不需要平衡调整,只需要将其子节点改为黑色即可。
如图: 依次插入节点[200,100,300,50]
对于删除不存在子节点的节点,这种情况最为复杂,需要平衡调整。
删除节点平衡调整有如下几种情况:
- 待删除节点的兄弟节点是红色
- 待删除节点的兄弟节点是黑色,且兄弟节点的子节点都是黑色
- 待删除节点的兄弟节点是黑色,如果兄弟节点在右侧,且兄弟节点的左子节点是红色,右子节点是黑色。如果兄弟节点在左侧,就是兄弟节点的右子节点是红色,左节点是黑色
- 待删除节点的兄弟节点是黑色,如果兄弟节点在右侧,且兄弟节点的右子节点是红色,左子节点是任意颜色。如果兄弟节点在左侧,则就是兄弟节点的左子节点是红色,右子节点是任意色
Case 1:
对于 Case 1,首先改变父节点和兄弟节点颜色(兄弟节点变为黑色,父节点变为红色),再对父节点做一次旋转(红色的兄弟节点在右侧,左旋。红色的兄弟节点在左侧,右旋),操作后,红黑的规则没有被破坏,树的黑色高度没变化,原兄弟节点的一个子节点变成删除节点的新兄弟节点。
所以,Case 1 转化成了 Case 2 或 Case 3、Case 4 几种情况。
如图: 依次插入节点[200,100,300,400,500,600] - Case1 转换为 Case2,3,4 等情况
Case 2:
对于Case 2,删除节点后其父节点的左子树比右子树黑高度小1,此时需要把兄弟节点改为红色,则左右子树黑高度就相同了。此时将删除节点的父节点变为新的节点,然后继续向上迭代调整,修正平衡。
如图: 依次插入节点[200,100,300,400,删除400] 得到下图结构
Case 3:
对于Case 3,兄弟节点在右侧时,交换兄弟节点和其左子节点(红色)的颜色,并对兄弟节点进行右旋,于是被删除节点的新兄弟是一个有红色右子节点的黑色节点。相反,兄弟节点在左侧时,交换兄弟节点和其右子节点(红色)的颜色,并对兄弟节点进行左旋,于是被删除节点的新兄弟是一个有红色左子节点的黑色节点。
所以,Case 3 转化成了Case 4。
如图: 依次插入节点[200,100,300,250]
Case 4:
ケース4、黒の兄弟、兄弟及び親スワップ色、兄弟セットの右の子ノードの右側のため、左回転を行うには、親ノードのノードを削除します。
左側、兄弟及び親スワップ色上の兄弟ノードが、左の子ノードは、黒取り巻きノード、右回転を行うには、親ノードのノードを削除します。
最後に、サイクルの終わりに直接ルートノード点を削除します。
図:ノードに挿入順次[200100300400]
擬似コード - 要素を削除バランス補正(ソースのJavaのTreeMap):
void fixAfterDeletion(Node<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
Node<K,V> sib = rightOf(parentOf(x));
// Case 1
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
// Case 2
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
// Case 3
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
// Case 4
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // 对称的
Node<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
すべて(わずかな変更を伴う)は、JavaのTreeMapコレクションからの抜粋記事では、これが決定的なリファレンスです。
なぜ、バランスの取れた赤黒木の木のような多くの用途を選択してください
あなたがバランスを維持するためには、バランスの取れたバイナリツリーをノードを挿入したり削除すると、バランスが大きなオーバーヘッドを必要とする、修正する必要があります。
例えば、AVL木の種類は、非常にバランスのとれたツリー構造は赤黒木よりバランスであり、これは、その容易ノード不均衡を追加または削除することを意味し、不均衡がオーバーヘッドが赤黒木よりも大きくなるときにノードを修正追加または削除する必要があります。しかし、それは否定できない効率AVLツリー検索が非常に安定しています。
大量のデータを挿入または削除する必要がある場合そのため、AVLはバランス調整周波数を必要と高くなります。そのため、赤黒ツリー内のノードを挿入し、削除し、より効率的に、シーンの多くを必要とします。当然のことながら、AVLバランスに起因し、従ってわずかに高いAVLの検索効率。
赤黒木は非常にバランスの取れた木ではありませんが、効果は非常に良いバランスとなっています。したがって、赤黒木は、妥協の選択、より良い全体的なパフォーマンスであると考えられています。
遂に
この目的のために導入された赤黒木は、学習プロセスを学ぶために、参照元をツリーマップすることができ、予期しない結果を持っています!
推奨:
赤黒木データ構造- (上)マップのプレゼンテーションを移動します
参考:
JDKのTreeMapソース
https://www.zhihu.com/question/20545708