Mercurial > hg > Papers > 2017 > tatsuki-master
view redBlackTree.tex @ 13:7acd7d5afeb6
commit
author | tatsuki |
---|---|
date | Tue, 07 Feb 2017 18:50:35 +0900 |
parents | 498b8f4175f9 |
children | 33d8077a5d45 |
line wrap: on
line source
\chapter{非破壊 TreeMap の実装} JungleのIndexは、Functional Javaの非破壊 TreeMap を用いて実装を行っている。 しかし、Functional Java の TreeMap は、木の変更の手間が大きい、並列実行時処理速度が落ちるなど、実用的な性能を持っていなかった。 そのため、Jungle の性能も、TreeMap部分がネックとなっていた。 その問題を解決するため、Jungleで使用する非破壊 TreeMap を作成した。 TreeMapは、Red Black Treeのアルゴリズム用いる。 \section{Red Black Tree} Red Black Treeは二分探索木の一つで、以下の条件を満たした木のことである。 \begin{enumerate} \item ノードは赤か黒の色を持つ。 \item ルートノードの色は黒。 \item 全ての葉は黒である。 \item 赤いノードの子は黒色である。 \item 全ての葉からルートまでのパスには、同じ個数の黒いノードがある。 \end{enumerate} Red Black Treeは、データの挿入、削除時に、上記の条件を崩さないように木のバランスを取る。 この条件により、Red Black Treeはデータの検索、削除、探索をO(log n)で行える。 \section{非破壊 TreeMap の定義} 非破壊 TreeMapは、Javaのジェネリクスを用いて、{\tt TreeMap<K,V>Key}と定義される。 TreeMapを作る際に、K,Vに任意の型を記述することで、 KeyとValueで使用する型を設定できる。 ソースコード\ref{src:TreeMapExample}に、 Key を String 型・ Value を ByteBuffer 型で定義するサンプルコードを記述する。 \begin{lstlisting}[frame=lrbt,numbers=left,label=src:TreeMapExample,caption=TreeMapの定義サンプル] TreeMap<String, ByteBuffer> map = new TreeMap<>(); \end{lstlisting} \newpage \section{非破壊 TreeMap のAPI} 非破壊 TreeMap は、値の挿入・削除を行うために、表\ref{RedBlackTreeAPI}に記述してあるAPIを提供している。 \begin{table}[htb] \begin{center} \caption{非破壊 TreeMap に実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\tt Node getRoot()} & TreeMap のルートノードを返す。 \\ \hline {\tt boolean isEmpty()} & TreeMap が値を保持していないなら{\tt true}を返す。 \\ \hline {\tt TreeMap<K,V> put(K key, V value)} & TreeMap に{\tt key:value} の組で値を挿入し、新しい TreeMap を返す。 \\ \hline {\tt TreeMap<K,V> delete(K key)} & TreeMap に{\tt key} とペアで格納されている値を削除し、新しい TreeMap を返す。\\ \hline {\tt Iterator<K> keys()} & TreeMap が保持している全ての {\tt key} を Iteratorで返す。 \\ \hline \end{tabular} \label{RedBlackTreeAPI} \end{center} \end{table} 非破壊 TreeMapは、put・deleteを行うと編集後の新しい TreeMap を返すため、新しい TreeMap で受ける必要がある(ソースコード\ref{src:TreeMapEditExample})。 この時返ってくる {\tt newMap}と、編集前の {\tt map} は別オブジェクトである。 \begin{lstlisting}[frame=lrbt,numbers=left,label=src:TreeMapEditExample,caption=TreeMapの編集例] TreeMap<String,String> map = new TreeMap<>(); TreeMap<String,String> newMap = map.put("key","value"); \end{lstlisting} \section{非破壊 Red Black Treeへのデータの挿入} 非破壊 Red Black Treeへのデータの挿入は、以下の手順で行われる。 \begin{enumerate} \item 挿入を行うノードと、現在のノードを比較する。 \item 比較の結果、大きかった場合右に、小さかった場合左のノードに進む。 \item 挿入を行う場所にたどり着くまで、1・2を繰り返す。 \item 現在の位置に、赤色でノードを挿入する。 \item 木のバランスを取りながら、ルートまでの経路の複製を行う。その際、変更が加えられないノードへは参照を行い過去の木と最大限共有を行う。 \end{enumerate} Red Black Treeのデータ挿入時のバランスは、次の5パターンに分けられる。 これ以降の説明では、挿入したノードは、ノードinsと記述する。 \section{データ挿入時のバランス ケース1} \begin{enumerate} \item ノードを挿入した位置がルート \end{enumerate} 挿入時上記の条件を満たしている場合、ルートノードの色を黒に変更することで木のバランスを取る。 ルートノードの色を黒に変更しても、左右のSub Treeの黒の個数が一つ増えるだけなので、Red Black Treeの条件は守られる。 \section{データ挿入時のバランス ケース2} \begin{enumerate} \item 挿入したノードinsの親ノードBが黒。 \end{enumerate} バランス時、上記の条件を満たしている場合、Red Black Treeの条件は崩れないため、赤色でノードを挿入して問題はない。%(図\ref{fig:RedBlackTreeInsert1})。 %\begin{figure}[htpb] %\begin{center} %\includegraphics[scale=0.5]{figures/RedBlackTreeInsert1.pdf} %\caption{データ挿入時のバランス2} %\label{fig:RedBlackTreeInsert1} %\end{center} %\end{figure} \section{データ挿入時のバランス ケース3} \begin{enumerate} \item 挿入したノードinsの親の親ノードAが黒。 \item ノードAの両方の子ノードB・Cが赤。 \end{enumerate} バランス時、上記の条件を満たしている場合、ノードB・Cを黒に、ノードAを赤に変更する(図\ref{fig:RedBlackTreeInsert2})。 また、本章の図に表記されている四角のノードの色は、Red Black Treeの条件を守られているなら問わず、それ以下の子ノードは省略してあるものとする。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.5]{figures/RedBlackTreeInsert2.pdf} \caption{データ挿入時のバランス3} \label{fig:RedBlackTreeInsert2} \end{center} \end{figure} バランス後、ノードAの色が変わってしまったため、ノードAを新しくノードinsとして再帰的に木のバランスを行う。 この変更は最悪の場合ルートまで続く。 \section{データ挿入時のバランス ケース4} \begin{enumerate} \item 挿入したノードinsの親の親ノードAが黒。 \item ノードAの、挿入したノード側の子ノードBが赤。 \item 逆側の子ノードCが黒。 \item ノードinsがノードBの木の中心側の子。 \end{enumerate} バランス時、上記の条件を満たしている場合、ノードBを中心に外側に回転処理を行い(図\ref{fig:RedBlackTreeInsert3})、次節に記述するバランス5を行う。 その際、ノードBをノードinsとして扱う。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.5]{figures/RedBlackTreeInsert3.pdf} \caption{データ挿入時のバランス4} \label{fig:RedBlackTreeInsert3} \end{center} \end{figure} \section{データ挿入時のバランス ケース5} \begin{enumerate} \item 挿入したノードinsの親の親ノードAが黒。 \item ノードAの、挿入したノード側の子ノードBが赤。 \item 逆側の子ノードCが黒 \item ノードinsがノードBの木の外側の子。 \end{enumerate} バランス時、上記の条件を満たしている場合、ノードAを中心に内側に回転処理を行い、回転後ノードAとノードBの色を入れ替える。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.5]{figures/RedBlackTreeInsert4.pdf} \caption{データ挿入時のバランス5} \label{fig:RedBlackTreeInsert4} \end{center} \end{figure} 赤黒木は、データ挿入時にこれらの処理を行うことで、木のバランスを取る。 \section{Red Black Treeのノード削除} Red Black Treeのノード削除は、以下の手順で行われる。 \begin{enumerate} \item 削除を行うノードと、現在のノードを比較する。 \item 比較の結果、大きかった場合右、小さかった場合左のノードに進む。 \item 削除を行うノードにたどり着くまで、1・2を繰り返す。 \item ノードに子が無い場合削除を行う。 \item 片側に部分木(子ノード)がある場合、ノードを削除した後、部分木のルートを昇格させる。 \item 両側に部分木(子ノード)がある場合は、左部分木の最大の値を持つノードの値を取得し、削除を行うノードと同じ色に変更した後、置換する。 \item 置換したノードを削除する(ここで削除させるノードは部分木の最大値であるため、子ノードは1つ以下、つまり削除の手順4・5どちらかの手順で削除できる)。 \item 削除したノードから、木のバランスを取りながら、ルートまでの経路の複製を行う。その際、変更が加えられないノードへは参照を行い過去の木と最大限共有を行う。 \end{enumerate} RedBlackTreeのバランスは、ノード削除時の状態によって、次の6パターンに分けられる。 また。これ以降削除したノードを、ノードdelと記述する。 \section{データ削除時のバランス ケース1} \begin{enumerate} \item ノードdelがルート。 \end{enumerate} 上記の条件を満たしている場合、全ての経路の黒ノード数が1つ減った状態で条件が成立しバランスは終了する。 \section{データ削除時のバランス ケース2} \begin{enumerate} \item ノードdelが黒。 \item ノードA・B・C・D・E・Fが黒。 \end{enumerate} バランス時、上記の条件を満たしている場合、ノードBを赤に変える(図\ref{fig:RedBlackTreeDelete1})。 そうすることで、ノードB・E・F以下の黒ノードの階層が減って、ノードA以下の木のバランスが回復する。 その後、Aを新たなノードdelとして木のバランスを行う。 このバランスは最悪の場合ルートまで続き、ケース2で終了する。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.45]{figures/RedBlackTreeDelete1.pdf} \caption{データ削除時のバランス2} \label{fig:RedBlackTreeDelete1} \end{center} \end{figure} \newpage \section{データ削除時のバランス ケース3} \begin{enumerate} \item ノードdelが黒。 \item ノードA・C・D・E・Fが黒 \item ノードBが赤。 \end{enumerate} 上記の条件を満たしている場合、ノードAを中心に外側に回転、その後ノードAを赤に、ノードBを黒に変更する(図\ref{fig:RedBlackTreeDelete2})。 その後、ノードdelを基準に再び木のバランスを行う。 この時のバランスは、図\ref{fig:RedBlackTreeDelete2}における、ノードEの子供の色に応じてケース4・5・6のどれかに帰着する。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.45]{figures/RedBlackTreeDelete2.pdf} \caption{データ削除時のバランス3} \label{fig:RedBlackTreeDelete2} \end{center} \end{figure} \newpage \section{データ削除時のバランス ケース4} \begin{enumerate} \item ノードdelが黒。 \item ノードB・C・D・E・Fが黒 \item ノードAが赤。 \end{enumerate} 上記の条件を満たしている場合、ノードAを黒に、ノードBを赤に変更する(図\ref{fig:RedBlackTreeDelete3})。 そうすることで、ノードA側の右側のSub Treeの黒の深さを変えることなく、左側のSub Treeの黒の深さが1つ増え、バランスが取れる。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.45]{figures/RedBlackTreeDelete3.pdf} \caption{データ削除時のバランス4} \label{fig:RedBlackTreeDelete3} \end{center} \end{figure} \newpage \section{データ削除時のバランス ケース5} \begin{enumerate} \item ノードdelが黒。 \item ノードB・C・D・Fが黒。 \item ノードEの色が赤。 \end{enumerate} 上記の条件を満たしている場合、ノードBを中心に外側に回転、その後、ノードEを黒に、ノードBを赤に変更する(図\ref{fig:RedBlackTreeDelete4})。 そして、データ削除時のバランス ケース7に帰着する。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.45]{figures/RedBlackTreeDelete4.pdf} \caption{データ削除時のバランス5} \label{fig:RedBlackTreeDelete4} \end{center} \end{figure} \newpage \section{データ削除時のバランス ケース6} \begin{enumerate} \item ノードdelが黒。 \item ノードB・C・Dが黒。 \item ノードFの色が赤。 \end{enumerate} 上記の条件を満たしている場合、ノードAを中心にノードdel側に回転、その後、ノードAとノードBの色を交換し、ノードFを黒にする(図\ref{fig:RedBlackTreeDelete5})。 そうすることで、ノードE・Fの黒の深さを変えること無く、ノードrepの黒の深さを1増やせるため、木のバランスが取れる。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.45]{figures/RedBlackTreeDelete5.pdf} \caption{データ削除時のバランス6} \label{fig:RedBlackTreeDelete5} \end{center} \end{figure} Red Black Treeは、削除時に上記のバランスを行うことで、木のバランスを保っている。 これらの機能を実装することで、非破壊TreeMapは完成した。