「Javaのシリーズの詳細なセット」私は新しいシリーズを書き始める準備ができてブログのタンプJavaの基本シリーズを終えた後。
これらの記事は、GitHubのの「Javaのインタビューガイド」の倉庫に私に手配します、よりエキサイティングなコンテンツが私の倉庫のビューにアクセスしてください
そのトラブルのスポットスター、フォークカザフスタン下のように
最初に私の個人ブログに登場した記事:
www.how2playlife.com
赤黒木は何ですか
まず、赤、黒の木は何ですか?赤黒木は、操作の最悪の場合の時間動的セットはO(LGN)であることを保証するために、古典的な効率的なアルゴリズムである「バランス」二分探索木です。各赤黒木ノードは5つのフィールド、それぞれ、色、キー、左、右、及びPを含有します。色の増加は、各ノード上の色は、ストレージノードを示し、または赤、黒であってもよいです。左のノードのキー値の値、左右のポインタのための右の子ノードは、pは、親ノードの部分を指すポインタであり、NILではありません。図は、以下の(ウィキペディア)赤黒木で表される、NILは、ノード外を指すポインタです。(ノードなどの節外点がキーではありません)
何が赤黒木自然の中で?一般に、赤と黒の性質、以下の5点と呼びます。
1)各ノードは、赤または黒です。
2)ルートノードは黒です。
3)各リーフノード(NIL)は黒です。
ノードが赤色の場合4)、その二人の子供は黒です。
5)各ノードについて、その子孫の他のノードへのノードからのすべてのパス上の黒のノードの同じ番号が含ま。
後者の分析のために、我々は次のような知識のポイントを知っている必要があります。
(1)ブラック高さ:ノードX(ノードが含まれていない)からのパスリーフノード、ブラック高さXと呼ばれるノードの黒ノード番号のいずれかの上に。
(2)2lgまでの高さ(N + 1)内のN個のノードの赤黒木があります。(キーワードで赤黒木ノードとして節点)
(3)N-ブラックツリーの高さを含む内部ノードがO(ログ(N))です。
定義
特別な赤黒木はバイナリ検索ツリー、すなわち、二分探索木の特徴を有する赤黒木、赤であるため、特別赤黒木はまた、RBの木(RED-BLACK-TREE)として知られている二分探索木、あります黒の木には、次の機能があります。
-
1.各ノードは黒または赤のいずれかであります
-
2.ルートは黒
-
3.各リーフノードは黒であり、ノードが空である(これを別の言い方であり、各リーフノードに2つの空の黒のノードである(ホイッスル兵士をいう)、ノードnもし唯一の子は、次に右の子、左Nホイッスルは、兵士であり、ノードNだけ右の子が、子がnホイッスル兵士が残っている場合)。
-
前記ノードが赤色の場合、その子ノードは黒でなければなりません
- 黒のノードの同じ数を含むノードのノード・パスのすべての子孫への1つのノードから5。
ノートには、いくつかのポイントがあります:
1. 3が空で赤黒木のノードで指定された各リーフノードの特性が、Javaには、ブラックリーフノードは、従って、赤黒木のトラバースさ見ることができないヌル代わっ空のノードを使用する赤黒木を達成するが、確認しリーフノードは赤です
2.パスがリーフにルートノードである(例えば、葉ノードのルートノードから最長経路が他の経路の2倍以下の長さであり、最短経路を黒赤黒木3の高さを確保するために4特長ノード)2(黒ノード - ノードブラック - 黒のノード)、最長パス4(黒のノードである - ノードが赤 - 黒のノード - ノードが赤 - 黒のノード)。
練習
赤黒木の操作
挿入
まず、挿入時赤黒木ノード、我々は挿入ノードの色に設定されている赤色挿入ノードと赤の存在下、以下の黒赤黒木の高さを変更する挿入ノードが黒の場合、5つの結合した逆特性、すなわち、状況:
1. 黒の父
示されるように、これは、任意の処理をせずに、すなわち特性赤黒木を破壊しません
2. レッド父
彼の父親は、次のような場合に赤になりますとき
- レッドトン
、実際には、比較的単純な、示されるように、唯一の親を変更することによって、第三の色は黒である赤tは、色が赤の祖先であり、再帰的にチェックノードの祖先に戻っ
- ブラックトン
黒tert-ブチルの場合、以下のように、平衡の影響によって色を変更することができないいくつかの状況下で、要約、及び従って回転操作することにより、赤と黒種の二種類の回転動作を疑う、左右(今存在しています)左、右利きの使用何時間を使用するとき
- ケース1:以下に示すように、[最初に(ノードTを変更することなく、その赤の2つのつの子ノードのために、黒ルートノードなければならない)色を変える、デキストロース]、ノードは、兵士笛を留意省略する
- ケース2:[左にケース1最初の場合、右利き、最終的な色の変化は、(ルートノードは黒でなければならず、その2つのつの子ノードがT変更することなく、ノードを赤)]以下に示すように、注目は省略するホイッスルの兵士ノード
- ケース3:以下に示すように、[L最初、最後に(ノードTを変更することなく、その赤の2つのつの子ノードの黒ルートノードなければならない)色を変更する]、ノードは、兵士笛を留意省略する
- ケース4:下記に示すようにケース3の右側に第一、その後回り左と、最終的な色変化(ルートノードは黒であり、その2つのつの子ノードする必要があり、Tを変更することなく、ノード赤)]、注意がホイッスルの兵士を省略するノード
これらは、すべての可能なアクションノード、赤黒木の削除次導入される新しい赤黒木です
削除
ノードの削除、挿入のより複雑な動作に比べて削除大きく3つのケースに分けることができます。
-
子ノードを削除すると、現在のノードが直接削除することができ、リーフノードであることをノード1ではありません
-
2.ノードが子ノードを持つ交換用アップを使用して、現在のノードとその子ノードを削除するには、この必要性を削除します。
- 3.削除ノードは、その後継ノード(ツリーノードにおける最小の要素よりも大きい)を見つける必要がある2つの子ノードを有し、次いで後続ノードに相当するノードにその後継ノードの内容をコピー代替ノードは、次のノードは、1だけ後続ノードを説明することができない以上、確かに左の子ノード、現在のノードの後継者を持っている場合は、後続ノードは、2つの子供ノード(この点は十分に理解されるべきではありませんことに留意すべきです子ノード)が存在する、または唯一の右の子ノード、すなわち、それほど問題は、このように1に変換されること。
修復操作程度に先立って、最初に、いくつかを理解する必要があります
ちょうど図に示す場合のように、単一のブランチノード赤黒木については、黒が現在のノードである1、赤色である子ノード(1、その二人の子供、現在のノードが赤色であると仮定すると孫ノードは、それが確かに黒点の種々の量で得られ、黒色であり、及び赤黒木不平衡場合ノード)2、黒色でなければなりません
2.由于红黑树是特殊的二叉查找树,它的删除和二叉查找树类型,真正的删除点即为删除点A的中序遍历的后继(前继也可以),通过红黑树的特性可知这个后继必然最多只能有一个孩子,其这个孩子节点必然是右孩子节点,从而为单支情况(即这个后继节点只能有一个红色孩子或没有孩子)
下面将详细介绍,在执行删除节点操作之后,将通过修复操作使得红黑树达到平衡的情况。
- Case 1:被删除的节点为红色,则这节点必定为叶子节点(首先这里的被删除的节点指的是真正删除的节点,通过上文得知的真正删除的节点要么是节点本身,要么是其后继节点,若是节点本身则必须为叶子节点,不为叶子节点的话其会有左右孩子,则真正删除的是其右孩子树上的最小值,若是后继节点,也必须为叶子节点,若不是则其也会有左右孩子,从而和2中相违背),这种情况下删除红色叶节点就可以了,不用进行其他的操作了。
- Case 2:被删除的节点是黑色,其子节点是红色,将其子节点顶替上来并改变其颜色为黑色,如下图所示
-
Case 3:被删除的节点是黑色,其子节点也是黑色,将其子节点顶替上来,变成了双黑的问题,此时有以下情况
- Case 1:新节点的兄弟节点为红色,此时若新节点在左边则做左旋操作,否则做右旋操作,之后再将其父节点颜色改变为红色,兄弟节点
从图中可以看出,操作之后红黑树并未达到平衡状态,而是变成的黑兄的情况
-
Case 2:新节点的兄弟节点为黑色,此时可能有如下情况
- 红父二黑侄:将父节点变成黑色,兄弟节点变成红色,新节点变成黑色即可,如下图所示
- 黑父二黑侄:将父节点变成新节点的颜色,新节点变成黑色,兄弟节点染成红色,还需要继续以父节点为判定点继续判断,如下图所示
- 红侄:
ここで、兄弟の右の部分木に新しいノード、赤色甥左サブツリーノード、このときの動作は右利きであり、兄弟ノードが黒親ノードに、色の父となり、以下のように、ノードは、甥ブラックなります図。
ケース2:右サブツリー内の新しいノード、兄弟ノードで甥が第1の左のために、この時点で、操作を右サブツリーを赤、右利き及び甥ノードがカラーの父となり、図に示すように、親ノードは、黒色となります。
三つの場合:左サブツリー内の新しいノードは、赤色甥は、図2に示すようにサブツリーを兄弟、左回りと右回り甥ノードにおける最初の操作のためのこの時間は、黒の親ノードへの色の父となり、左。
4つの場合:右サブツリー内の新しいノード、右サブツリー赤色甥の兄弟ノードは、この場合、左利きの操作を、親ノードの色の兄弟ノードの親ノード黒くなる、甥ノードが黒になり、次図。
赤黒木の実装
これは、挿入、削除、L、Dを含め、赤黒木プロセスとしてトラバーサル操作をJavaコードを使用して実装されています
挿入
/* 插入一个节点
* @param node
*/
private void insert(RBTreeNode<T> node){
int cmp;
RBTreeNode<T> root = this.rootNode;
RBTreeNode<T> parent = null;
//定位节点添加到哪个父节点下
while(null != root){
parent = root;
cmp = node.key.compareTo(root.key);
if (cmp < 0){
root = root.left;
} else {
root = root.right;
}
}
node.parent = parent;
//表示当前没一个节点,那么就当新增的节点为根节点
if (null == parent){
this.rootNode = node;
} else {
//找出在当前父节点下新增节点的位置
cmp = node.key.compareTo(parent.key);
if (cmp < 0){
parent.left = node;
} else {
parent.right = node;
}
}
//设置插入节点的颜色为红色
node.color = COLOR_RED;
//修正为红黑树
insertFixUp(node);
}
/**
* 红黑树插入修正
* @param node
*/
private void insertFixUp(RBTreeNode<T> node){
RBTreeNode<T> parent,gparent;
//节点的父节点存在并且为红色
while( ((parent = getParent(node)) != null) && isRed(parent)){
gparent = getParent(parent);
//如果其祖父节点是空怎么处理
// 若父节点是祖父节点的左孩子
if(parent == gparent.left){
RBTreeNode<T> uncle = gparent.right;
if ((null != uncle) && isRed(uncle)){
setColorBlack(uncle);
setColorBlack(parent);
setColorRed(gparent);
node = gparent;
continue;
}
if (parent.right == node){
RBTreeNode<T> tmp;
leftRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
setColorBlack(parent);
setColorRed(gparent);
rightRotate(gparent);
} else {
RBTreeNode<T> uncle = gparent.left;
if ((null != uncle) && isRed(uncle)){
setColorBlack(uncle);
setColorBlack(parent);
setColorRed(gparent);
node = gparent;
continue;
}
if (parent.left == node){
RBTreeNode<T> tmp;
rightRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
setColorBlack(parent);
setColorRed(gparent);
leftRotate(gparent);
}
}
setColorBlack(this.rootNode);
}
次のステップでノードを挿入する操作:
-
1.場所:仕上げ赤黒ツリートラバーサル、付加の位置を決定することは、挿入方法としてコード位置が追加で発見されます
- 2.修理:これは、上記に記載され、添加元素は赤黒木の回転を調整するために必要なその色によって、その特性を満たさない赤黒木、、、方法上記すなわちコードであってもよいinsertFixUp
[ノードの削除
削除ノードに次のコード
private void remove(RBTreeNode<T> node){
RBTreeNode<T> child,parent;
boolean color;
//被删除节点左右孩子都不为空的情况
if ((null != node.left) && (null != node.right)){
//获取到被删除节点的后继节点
RBTreeNode<T> replace = node;
replace = replace.right;
while(null != replace.left){
replace = replace.left;
}
//node节点不是根节点
if (null != getParent(node)){
//node是左节点
if (getParent(node).left == node){
getParent(node).left = replace;
} else {
getParent(node).right = replace;
}
} else {
this.rootNode = replace;
}
child = replace.right;
parent = getParent(replace);
color = getColor(replace);
if (parent == node){
parent = replace;
} else {
if (null != child){
setParent(child,parent);
}
parent.left = child;
replace.right = node.right;
setParent(node.right, replace);
}
replace.parent = node.parent;
replace.color = node.color;
replace.left = node.left;
node.left.parent = replace;
if (color == COLOR_BLACK){
removeFixUp(child,parent);
}
node = null;
return;
}
if (null != node.left){
child = node.left;
} else {
child = node.right;
}
parent = node.parent;
color = node.color;
if (null != child){
child.parent = parent;
}
if (null != parent){
if (parent.left == node){
parent.left = child;
} else {
parent.right = child;
}
} else {
this.rootNode = child;
}
if (color == COLOR_BLACK){
removeFixUp(child, parent);
}
node = null;
}
/**
* 删除修复
* @param node
* @param parent
*/
private void removeFixUp(RBTreeNode<T> node, RBTreeNode<T> parent){
RBTreeNode<T> other;
//node不为空且为黑色,并且不为根节点
while ((null == node || isBlack(node)) && (node != this.rootNode) ){
//node是父节点的左孩子
if (node == parent.left){
//获取到其右孩子
other = parent.right;
//node节点的兄弟节点是红色
if (isRed(other)){
setColorBlack(other);
setColorRed(parent);
leftRotate(parent);
other = parent.right;
}
//node节点的兄弟节点是黑色,且兄弟节点的两个孩子节点也是黑色
if ((other.left == null || isBlack(other.left)) &&
(other.right == null || isBlack(other.right))){
setColorRed(other);
node = parent;
parent = getParent(node);
} else {
//node节点的兄弟节点是黑色,且兄弟节点的右孩子是红色
if (null == other.right || isBlack(other.right)){
setColorBlack(other.left);
setColorRed(other);
rightRotate(other);
other = parent.right;
}
//node节点的兄弟节点是黑色,且兄弟节点的右孩子是红色,左孩子是任意颜色
setColor(other, getColor(parent));
setColorBlack(parent);
setColorBlack(other.right);
leftRotate(parent);
node = this.rootNode;
break;
}
} else {
other = parent.left;
if (isRed(other)){
setColorBlack(other);
setColorRed(parent);
rightRotate(parent);
other = parent.left;
}
if ((null == other.left || isBlack(other.left)) &&
(null == other.right || isBlack(other.right))){
setColorRed(other);
node = parent;
parent = getParent(node);
} else {
if (null == other.left || isBlack(other.left)){
setColorBlack(other.right);
setColorRed(other);
leftRotate(other);
other = parent.left;
}
setColor(other,getColor(parent));
setColorBlack(parent);
setColorBlack(other.left);
rightRotate(parent);
node = this.rootNode;
break;
}
}
}
if (node!=null)
setColorBlack(node);
}
ノードを削除するに取引対応を行うためにいくつかの状況に分かれています。
- 1.削除ノード、ノードを削除するために次の3つの場合に応じて
- 1.実際にノードを削除するには、子を持ちません
- 2.実際に削除されたノードが子ノードを持っています
- 3.削除するノードは、2つのつの子ノードを持っています
- 2.修正は赤黒ようremoveFixUp方法コード修正赤黒木プロパティを呼び出すようツリー特性を、。
3.まとめ
上記は、長い時間を書き込む処理内部業務の一部詳細な分析を含む赤黒木の特性のいくつかを説明し、私ははっきりと理由に本当に多くの困難を感じます。背後には問題がある場合はどこ、私を修正してください、より深く理解していきます。
参考記事
JDK研究赤黒木のソースコードを解析することによって達成アルゴリズムのTreeMap