「赤黒木」の版間の差分
Solareenlo (会話 | 投稿記録) m →性質: Anchors を追加 |
Solareenlo (会話 | 投稿記録) →用語: 黒深度と黒高さを追加 |
||
23行目: | 23行目: | ||
== 用語 == |
== 用語 == |
||
{{Multiple image |
|||
|direction= vertical |
|||
|header=赤黒木の例 |
|||
|state=expanded |
|||
|width=500 |
|||
|image1=Red-black tree example.svg |
|||
|caption1=図1: 明示的なNILの葉を持つ赤黒木 |
|||
|image2=Red-black tree example with sockets.svg |
|||
|caption2=図2: 左右の暗黙のドッキングポイントを持つ赤黒木 |
|||
}} |
|||
赤黒木は[[二分木]]の一種であり、[[コンピュータ科学]]において数などの[[比較]]可能な[[データ]]を組織化する際に用いられる。データは二分木の[[頂点 (グラフ理論)|ノード]]に配置され、そのうちでスタート地点となる「どのノードの[[子]]でもないノード」を[[根]]という。根は2つまでの「子」(根に接続しているノード)をもつことができる。そして、その子もまた2つまで子をもつことができ、その子も……、以下同様である。このようにして、根から、他の木内のノードへの経路ができる。 |
赤黒木は[[二分木]]の一種であり、[[コンピュータ科学]]において数などの[[比較]]可能な[[データ]]を組織化する際に用いられる。データは二分木の[[頂点 (グラフ理論)|ノード]]に配置され、そのうちでスタート地点となる「どのノードの[[子]]でもないノード」を[[根]]という。根は2つまでの「子」(根に接続しているノード)をもつことができる。そして、その子もまた2つまで子をもつことができ、その子も……、以下同様である。このようにして、根から、他の木内のノードへの経路ができる。 |
||
33行目: | 43行目: | ||
という性質をもつようにつくられる。これによって、木の中から特定の値をさがすことや、すべての値を順番にあたることなどが素早くできるわけである。 |
という性質をもつようにつくられる。これによって、木の中から特定の値をさがすことや、すべての値を順番にあたることなどが素早くできるわけである。 |
||
ノードの黒深度は、根からそのノードまでの黒ノードの数(すなわち黒祖先の数)と定義される。赤黒木の黒高さは、根から葉までのどのパスにもある黒のノードの数であり、[[#req5|要件5]]により一定である(代わりに、任意の葉のノードの黒深度として定義することもできる)。 <ref name="Mehlhorn2008">{{cite book |
|||
|chapter=7. Sorted Sequences |
|||
|chapter-url=http://people.mpi-inf.mpg.de/~mehlhorn/ftp/Toolbox/SortedSequences.pdf |
|||
|title=Algorithms and Data Structures: The Basic Toolbox |
|||
|url=http://people.mpi-inf.mpg.de/~mehlhorn/Toolbox.html |
|||
|last1=Mehlhorn |first1=Kurt |author-link1=Kurt Mehlhorn |
|||
|last2=Sanders|first2=Peter |author-link2=Peter Sanders (computer scientist) |
|||
|publisher=Springer |location= Berlin/Heidelberg |
|||
|year=2008 |
|||
|isbn=978-3-540-77977-3 |
|||
|doi=10.1007/978-3-540-77978-0 |citeseerx=10.1.1.148.2305 |
|||
}}</ref>{{rp|154–165}} |
|||
ノードの黒高さは、そのノードが根とする部分木の黒高さである。この記事では、NILノードの黒高さは0とする。なぜなら、その部分木は図2が示唆するように空であり、その木の高さも0であるからである。 |
|||
== 用途と利点 == |
== 用途と利点 == |
2021年12月22日 (水) 08:55時点における版
赤黒木 | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
種類 | 木構造 | ||||||||||||||||||||
発表時期 | 1972 | ||||||||||||||||||||
発明者 | en:Rudolf Bayer | ||||||||||||||||||||
ビッグオー記法による計算量 (en) | |||||||||||||||||||||
|
赤黒木(あかくろぎ)は、コンピュータ科学のデータ構造である平衡二分木の一種で、主に連想配列の実装に用いられている。2色木、レッド・ブラック・ツリーともいう。
このデータ構造は1972年のルドルフ・ベイヤー (en:Rudolf Bayer) の発明である"symmetric binary B-trees"が元となっており、赤黒木という名前自体は 1978年にレオニダス・ギッバス (en:Leonidas J. Guibas) とロバート・セジウィック (en:Robert Sedgewick) によって発表された論文による。
赤黒木は、探索、挿入、削除などの操作における最悪時間計算量がO(log n)(nはツリーの要素数)と短く、複雑ではあるが実用的なデータ構造として知られている。
この日本語版は概要のみの解説であり、具体的なアルゴリズムはwikipedia英語版(Red-black_tree)に掲載されている。
用語
赤黒木は二分木の一種であり、コンピュータ科学において数などの比較可能なデータを組織化する際に用いられる。データは二分木のノードに配置され、そのうちでスタート地点となる「どのノードの子でもないノード」を根という。根は2つまでの「子」(根に接続しているノード)をもつことができる。そして、その子もまた2つまで子をもつことができ、その子も……、以下同様である。このようにして、根から、他の木内のノードへの経路ができる。
赤黒木に置ける葉はデータを持たないノードである。この葉は実際にメモリ上に置かれる必要はなくヌルポインタで表すこともできるが、独立のノードとみなしたほうがいくつかのアルゴリズムの記述が簡単になる。 また、部分木とは、木のうちある一つのノードから到達可能な部分を取り出して一つの木とみたとき、その取り出した木をいう。
赤黒木は二分探索木であり、すなわち、各ノードのもつ値が
- そのノードの右部分木に含まれるノードのもつ値より大きくない
- そのノードの左部分木に含まれるノードのもつ値より小さくない
という性質をもつようにつくられる。これによって、木の中から特定の値をさがすことや、すべての値を順番にあたることなどが素早くできるわけである。
ノードの黒深度は、根からそのノードまでの黒ノードの数(すなわち黒祖先の数)と定義される。赤黒木の黒高さは、根から葉までのどのパスにもある黒のノードの数であり、要件5により一定である(代わりに、任意の葉のノードの黒深度として定義することもできる)。 [3]:154–165 ノードの黒高さは、そのノードが根とする部分木の黒高さである。この記事では、NILノードの黒高さは0とする。なぜなら、その部分木は図2が示唆するように空であり、その木の高さも0であるからである。
用途と利点
赤黒木というデータ構造は、最悪のケースにおける計算量が、データの挿入・削除・検索のいずれにおいても、データ構造のうちで最善のものの一つである。このため、リアルタイム・コンピューティングのような時間計算量に敏感なアプリケーションにおいて有益である。また、計算幾何学で用いるデータ構造など、最悪のケースでの計算量を保証する必要のあるデータ構造の基礎としても有用なことが多い。 赤黒木と同様に平衡二分木であるAVL木は、赤黒木に比べて平衡性についての条件が厳しく、そのためAVL木では最悪のケースを想定すると挿入や削除の際の回転操作の回数が赤黒木より多くなってしまう。
赤黒木は関数型プログラミングにおいても部分的に重要であり、永続的データ構造を実現するデータ構造の一つとして、変更後も以前の値を保持し続けるような連想配列や集合を実装する際に広く用いられるものの一つである。なお、このような永続性をもったタイプの赤黒木では、挿入や削除のたびに、時間だけでなく のオーダーの空間が必要となる。
赤黒木は2-3-4木にアイソメトリックである。すなわち、任意の2-3-4木に対して、少なくとも一つの赤黒木でその2-3-4木と同じデータを同じ順序でもつものが存在する。このとき、2-3-4木における挿入および削除の各操作は、赤黒木におけるカラー・フリッピングおよび回転の各操作に等価であり、そのため、2-3-4木を考えることは赤黒木の背景にある理論を理解する上で重要である。実用性の高くない2-3-4木が、アルゴリズムの入門書において赤黒木の直前によく紹介されているのは、このためである。
性質
赤黒木は、各ノードに「赤」または「黒」という「色」をつけた二分探索木であり、赤黒木として有効なものであるためには、通常の二分探索木としての条件のほか、以下の条件が要求される。[4]
- 各ノードは赤か黒の色をもつ。
- 根は黒である (この条件はしばし省かれる。根は赤から黒に変えることはできるので、解析にはほとんど影響しない)。
- 葉 (NIL) はすべて黒である。葉はすべて根と同じ色である。
- 赤のノードは黒ノードを2つ子に持つ(したがって、赤のノードの親ノードは黒である)。
- 任意のノードについて、そのノードから子孫の葉までの道に含まれる黒いノードの数は、選んだ葉によらず一定である(この条件は、「根から葉までの道に含まれる黒いノードの数は、葉によらず一定である」と言い換えることができる)。
これらの条件から、次の赤黒木の重要な性質が導かれる。
- 根から葉まで道で最長のものの長さは、根から葉までの道で最短のものの長さの二倍を超えない。
これは、赤黒木がある程度の平衡性をもっているということである。挿入・削除・検索といった操作は最悪のケースでは木の高さに比例した時間を要するので、このような赤黒木の性質から理論的に最悪時間計算量の見積もりが立つことになる。これは通常の二分探索木と異なる赤黒木の特徴である。
先ほどの条件からなぜ今の性質が導かれるのかを確認するには、条件4からわかる「赤黒木の根から葉までの道において赤のノードが2つ続くことはない」という性質がカギになる。すなわち、条件をみたす最短の道というのはすべて黒のノードからなる道であり、条件を満たす最長の道というのは赤と黒のノードが交互に現れるような道となる。そして、条件5より、根から葉までの道がすべて同じ個数の黒のノードを含むことから、最長の道の長さは最短の道の長さの二倍を超えないという上記の性質が導かれる。(ここで、根と葉は黒のノードである。)
一般に、データの木構造 (データ構造)による表現では、ノードが一つだけの子をもつことや、葉ノードがデータをもつこと(言い換えれば、データをもつノードが子をもたないこと)を可能としている場合も多い。赤黒木においてもそのような考え方を導入することができないわけではないが、それをすると先ほどの性質をいくつかの点で修正する必要が生じ、またアルゴリズムが複雑になってしまう。そのため、この記事においては、今まで説明したように「空の葉」(nil leaf、null leaf)を用い、葉はデータをもたずただ木の終端を意味するだけのノードであるとした。なお、このような葉ノードは図をかく際には省略することも多く、その結果、赤黒木が先ほどの条件をみたさないように見えることがある。しかしもちろん、実際には内部の(つまり、葉ではない)ノードはすべて、空の葉を含めて2つの子をもっているわけである。
ところで、赤黒木について、二分木の(ノードではなく)辺に色がついたものであるという説明がなされていることもある。これは、この記事における「ノードの色」を「ノードとその親とを連結する辺の色」に読み替えたものである(ただし、「根ノードの色」に対応するものはないため、この記事での条件2にあたる条件が不要となる)。
アプリケーションと関連するデータ構造
赤黒木は挿入時間、削除時間、探索時間において、最悪のケースを保証する。これはリアルタイムシステムのような時間センシティブなアプリケーションにおいて価値あるのみならず、最悪のケースを保証する他のデータ構造における価値ある部品となっている。 例えば、計算幾何学で用いられる多くのデータ構造は赤黒木をベースとしているし、現行のLinuxカーネルで用いられる CFS (Completely Fair Scheduler)では赤黒木を使用している。
AVL木は の探索、挿入、削除をサポートする、もう一つのデータ構造である。AVL木は、赤黒木よりも厳密な平衡性を持っているため、挿入や削除は遅くなるが、検索は早くなる。このため、AVL木は一度構築して、再構成する必要のないデータ構造にとっては魅力的である。例えば、言語辞書(または、アセンブラやインタープリタの命令コードのようなプログラム辞書)などのように。
また、赤黒木はAVL木よりも条件が多いため、実装が面倒である。
赤黒木はまた、最も一般的な永続データ構造のひとつであり、関数型言語で特に価値がある。変化後に前のバージョンを保持できるように、連想配列や集合 (プログラミング)を構築するのに使われる。赤黒木の永続バージョンは、各挿入や各削除において の(時間に加えて)空間を要する。
操作
すべての赤黒木は単純な二分探索木の特殊なケースであるため、赤黒木に対する探索や木の走査などの読み取り専用操作は、二分探索木で使われるものと何の変更もない。しかし、挿入や削除の直後は赤黒木の性質に反する場合があるため、これを修復して赤黒木を平衡にすることをリバランシングという。操作における最悪時間計算量は、O記法で ( はツリーの要素数)、平均では、 または償却された 、色の変更には定数(実際には非常に速い)[5]:310 [3]:158と小さい数になり、木の回転は3回以内(挿入は2回)となる。
挿入と削除の操作の詳細は、例としてC++のコードを用いて説明する。サンプルコードでは、以下の型定義とマクロ、および回転のためのヘルパー関数を使用する。
// 基本の型定義:
enum color_t { BLACK, RED };
struct RBnode { // 赤黒木のノード
RBnode* parent; // == NULL (木のルートの時)
RBnode* child[2]; // == NIL (子が空の時)
// Index is:
// LEFT := 0, if (key < parent->key)
// RIGHT := 1, if (key > parent->key)
enum color_t color;
int key;
};
#define NIL NULL // ヌルポインタまたは番兵ノードへのポインタ
#define LEFT 0
#define RIGHT 1
#define left child[LEFT]
#define right child[RIGHT]
struct RBtree { // 赤黒木
RBnode* root; // == NIL (木が空の時)
};
// ルートでない非NILの RBnode* N の子方向(∈ { LEFT, RIGHT })を取得する
#define childDir(N) ( N == (N->parent)->right ? RIGHT : LEFT )
RBnode* RotateDirRoot(
RBtree* T, // 赤黒木
RBnode* P, // 部分木の根 (Tの根であってもよい)
int dir) { // dir ∈ { LEFT, RIGHT }
RBnode* G = P->parent;
RBnode* S = P->child[1-dir];
RBnode* C;
assert(S != NIL); // 真のノードへのポインターを要求する
C = S->child[dir];
P->child[1-dir] = C; if (C != NIL) C->parent = P;
S->child[ dir] = P; P->parent = S;
S->parent = G;
if (G != NULL)
G->child[ P == G->right ? RIGHT : LEFT ] = S;
else
T->root = S;
return S; // 部分木の新しい根
}
#define RotateDir(N,dir) RotateDirRoot(T,N,dir)
#define RotateLeft(N) RotateDirRoot(T,N,LEFT)
#define RotateRight(N) RotateDirRoot(T,N,RIGHT)
関連項目
外部リンク
- 情報処理概論(Java) - ウェイバックマシン(2016年5月1日アーカイブ分)
- ^ a b c d James Paton. “Red–Black Trees”. 2021年12月22日閲覧。
- ^ a b リバラシングのみ (検索は無し)、Tarjan and Mehlhornを参照。
- ^ a b Mehlhorn, Kurt; Sanders, Peter (2008). “7. Sorted Sequences”. Algorithms and Data Structures: The Basic Toolbox. Berlin/Heidelberg: Springer. doi:10.1007/978-3-540-77978-0. ISBN 978-3-540-77977-3
- ^ Cormen, Thomas; Leiserson, Charles; Rivest, Ronald; Stein, Clifford (2009). “13. Red–Black Trees”. Introduction to Algorithms (3rd ed.). MIT Press. pp. 308–309. ISBN 978-0-262-03384-8
- ^ Tarjan, Robert Endre (April 1985). “Amortized Computational Complexity”. SIAM Journal on Algebraic and Discrete Methods 6 (2): 306–318. doi:10.1137/0606031 .