コンピュータサイエンスにおいて、区間木は区間を保持するための木構造のデータ構造です。具体的には、任意の区間または点と重なるすべての区間を効率的に見つけることができます。これは、ウィンドウクエリによく使用されます。[ 1 ]たとえば、長方形のビューポート内のコンピュータ化された地図上のすべての道路を検索したり、3 次元シーン内のすべての可視要素を検索したりする場合などです。同様のデータ構造として、セグメント木があります。
簡単な解決策は、各区間を訪れて、それが与えられた点または区間と交差するかどうかをテストすることです。時間、はコレクション内の区間の数です。クエリがコレクション内のすべての区間と交差する大きな区間である場合など、クエリがすべての区間を返す可能性があるため、これは漸近的に最適です。ただし、実行時間が で表される出力依存アルゴリズムでは、(クエリによって生成される区間の数)も考慮に入れることができます。区間ツリーのクエリ時間はそして最初の作成時間はメモリ消費を制限しながら作成後、区間ツリーは動的になり、区間の効率的な挿入と削除が可能になります。時間。区間の端点が小さな整数範囲内にある場合(例:範囲内))より高速で、実際には最適なデータ構造が存在する[ 2 ] [ 3 ]。前処理時間はクエリ時間報告用特定のクエリポイントを含む区間(非常に単純な例については[ 2 ]を参照)。
単純なケースでは、区間は重複せず、単純な二分探索木に挿入してクエリを実行できます。時間。しかし、任意に重なり合う区間では、開始点または終了点でソートされた順序が異なる可能性があるため、ツリーに挿入するために2つの区間を比較する方法はありません。素朴なアプローチとしては、各区間の開始点で順序付けられたツリーと終了点で順序付けられたツリーの2つの並列ツリーを構築することが考えられます。これにより、各ツリーの半分を破棄することができます。時間を要するが、結果を統合する必要がある。時間。これはクエリを返します。それは力ずくと何ら変わらない。
区間木はこの問題を解決します。この記事では、中心区間木と拡張区間木と呼ばれる、区間木の2つの代替設計について説明します。
クエリには時間とともに、区間の総数であり、報告された結果の数である。建設には時間とストレージには空間。
与えられたセット数直線上の区間を扱う場合、他の区間や点と重なるすべての区間を効率的に取得できるように、データ構造を構築する必要があります。
まず、すべての区間の全範囲を取り、(実際には、 木のバランスを保つために選択する必要があります。これにより、3 つの間隔のセットが得られます。 と呼ばれる完全に右派のと呼ばれる 重複するものと呼ばれる。
間隔 そして 区間がなくなるまで、同じ方法で再帰的に分割されます。
間隔中心点と重なる区間は、区間ツリーのノードにリンクされた別のデータ構造に格納されます。このデータ構造は2つのリストで構成され、1つは開始点順に並べられたすべての区間、もう1つは終了点順に並べられたすべての区間を含みます。
結果として、各ノードに以下の情報を格納する二分木が得られます。
区間木を用いると、任意の入力と重なるすべての範囲を迅速に計算できる。
課題は、与えられた点と重なるツリー内のすべての区間を見つけることです。ツリーは、従来の二分木を走査するのと同様の再帰アルゴリズムで走査されますが、各ノードの「中心」点と重なる区間を検索するための追加ロジックが組み込まれています。
各ツリーノードについて、と比較される 上記のノード構築で使用された中間点。より小さい 、最も左の区間のセット、 、考慮されます。より大きい 最も右側の区間のセット、 考慮される。

ツリーがルートからリーフへとたどられるにつれて各ノードが処理されるにつれて、その範囲は 処理されます。より小さい 、すべての区間 終了しなければならないあるいは、それらは重複することもできない。 したがって、次の区間を見つけるだけでよい。始まる前に. リスト既に構築されているものを参照できます。このシナリオでは区間の開始点のみが関係するため、リストを開始点でソートできます。最も近い数値が以下であればこのリストに見つかった場合、リストの先頭から見つかったポイントまでのすべての範囲が重なりますなぜなら、それらは前に始まるからそして、その後終了する(重複しているため)これはより大きいしたがって、開始点の値がを超えるまでリスト内の区間を列挙することができます。。
同様に、より大きい 、すべての区間 開始前に開始する必要があります、したがって、区間の末尾でソートされたリストを使用することで見つけることができます。
もし完全に一致する、すべての区間追加処理なしで結果に追加でき、ツリー走査を停止できます。
結果間隔の場合クエリ区間と交差する以下のいずれかが成り立つ必要があります。
まず、開始点および/または終了点が 内にあるすべての区間を見つけます別々に構築されたツリーを使用します。1 次元の場合、区間セット内のすべての開始点と終了点を含む探索ツリーを使用できます。各開始点と終了点には、対応する区間へのポインタがあります。バイナリ サーチは、開始と終了の時間考慮すべき最小値と最大値を示します。この範囲内の各点は、重なり合う区間を参照します。そして結果リストに追加されます。期間の開始と終了が両方とも同じ範囲内にある可能性があるため、重複を避けるように注意する必要があります。これは、各区間にバイナリフラグを使用して、結果セットに追加されたかどうかをマークすることで実現できます。
最後に、次の区間を囲む区間を見つける必要があります。これらを見つけるには、内部の任意の点を選択します。そして、上記のアルゴリズムを使用して、その点と交差するすべての区間を見つけます(ここでも、重複を削除するように注意してください)。
区間木データ構造は、より高次元に一般化することができる。クエリと構築時間が同一で、空間。
まず、レンジツリークエリ領域内に開始点と終了点を持つすべての区間を効率的に取得できる次元が構築されています。対応する範囲が見つかったら、残っているのは、ある次元で領域を囲む範囲だけです。これらの重なりを見つけるには、 区間ツリーが作成され、1 つの軸が交差しますそれぞれについてクエリが実行されます。たとえば、2 次元では、正方形の底辺がクエリされます。 (または交差する他の水平線))は、水平軸用に構築された区間ツリーに対してクエリされます。同様に、左側の(または交差する他の垂直線))は、垂直軸上に構築された区間木に対してクエリされます。
各区間木には、より高次元のための追加も必要です。木をたどる各ノードでは、と比較される 重なりを見つける。1 次元の場合に使用された 2 つのソートされた点のリストの代わりに、範囲ツリーが構築される。これにより、すべての点を効率的に取得できる。 重複領域。
ツリーからある区間を削除した後、その区間を含むノードに他の区間がなくなった場合、そのノードはツリーから削除される可能性があります。これは、通常の二分木の削除操作よりも複雑です。
区間は、ツリー内の複数のノードの中心点と重なる場合があります。各ノードは、自身と重なる区間を格納しており、左サブツリーでは中心点の左側にあるすべての区間が、右サブツリーでは同様に格納されるため、各区間は、その中心点が重なるノードの集合の中で、ルートに最も近いノードに格納されることになります。
二分木における通常の削除操作(削除対象のノードが2つの子を持つ場合)では、葉ノードからさらに離れたノードを削除対象のノードの位置(通常は右部分木の最も左の子、または左部分木の最も右の子)まで昇格させる。

この昇格の結果、昇格したノードより上位にあったノードの一部が、昇格したノードの子孫になります。これらのノードの中から、昇格したノードと重なる区間を探し出し、それらの区間を昇格したノードに移動させる必要があります。その結果、新たな空のノードが生じる可能性があり、それらは同じアルゴリズムに従って削除しなければなりません。
削除に影響を与えるのと同じ問題が回転操作にも影響します。回転操作では、ノードが可能な限りルートに近い場所に格納されるという不変条件を維持する必要があります。

区間を表現する別の方法は、Cormen et al. (2009 、セクション 14.3: 区間ツリー、pp. 348 – 354)で説明されています。
挿入と削除の両方には時間とともに、挿入または削除操作前のツリー内の区間の総数。
拡張ツリーは、例えば二分探索木や自己平衡二分探索木のような単純な順序付きツリーから構築でき、区間の「低い」値で順序付けられます。次に、各ノードに追加の注釈が追加され、そのノードから下のすべての区間の中で最大の上限値が記録されます。この属性を維持するには、ノードが追加または削除されるたびに、ノードのすべての祖先を下から上に更新する必要があります。これは、ノードの追加または削除ごとに O( h ) ステップしかかかりません。ここで、hはツリーに追加または削除されるノードの高さです。挿入および削除中にツリーの回転がある場合は、影響を受けるノードも更新する必要がある場合があります。
さて、2つの区間が知られていますそして両方が重なる場合のみそして指定された区間と重複するノードをツリー内で検索する場合、次のノードをすぐにスキップできます。
ツリーが不要な走査を避けることで、パフォーマンスが向上する場合があります。これは、既に存在する区間を追加したり、存在しない区間を削除したりする際に発生する可能性があります。
区間に対しては、まず下限値で、次に上限値で順序付けすることにより、全順序を定義できます。次に、メンバーシップチェックを実行できます。時間、対重複を見つけるのに必要な時間区間は挿入または削除する区間と重複します。このソリューションの利点は、追加の構造を必要としないことです。変更は厳密にはアルゴリズム的なものです。欠点は、メンバーシップクエリに時間がかかることです。時間。
あるいは、メモリ使用量と期待定数時間でのメンバーシップクエリは、区間ツリーと同期して更新されるハッシュテーブルを使用することで実装できます。区間を値ではなく参照で格納する場合、必ずしも総メモリ使用量が2倍になるとは限りません。
各ノードのキーは区間そのものであり、したがってノードはまず値の小さい順に、最後に値が大きい順に並べられ、各ノードの値は区間の終点となる。
public void add ( Interval i ) { put ( i , i.getEnd ( )) ; }区間を検索するには、キー(n.getKey())と最大値(n.getValue())を使用してツリーをたどり、クエリと重複しないブランチを除外します。最も単純なケースは、ポイントクエリです。
// ノード「n」から始めて、「p」を含むすべての区間を検索し、一致する区間をリスト「result」に追加します。public void search ( IntervalNode n , Point p , List < Interval > result ) { // 存在しないノードは検索しませんif ( n == null ) return ;// p がこのノードおよびすべての子ノード内の任意の区間の右端の点よりも右にある場合、// 一致するものはありません。if ( p . compareTo ( n . getValue ()) > 0 ) return ;// 左の子要素を検索search ( n . getLeft (), p , result );// このノードをチェックします。もし (n.getKey ( ) . contains ( p ) )ならば、result.add ( n.getKey ( ) ) ;// p がこの区間の開始位置の左側にある場合、// 右側のどの子要素にも含まれません。if ( p . compareTo ( n . getKey (). getStart ()) < 0 ) return ;// それ以外の場合は、右の子要素を検索するsearch (n.getRight ( ) , p , result ) ; }どこ
a.compareTo(b)a < b の場合、負の値を返します。a.compareTo(b)a = b の場合、ゼロを返します。a.compareTo(b)a > b の場合、正の値を返します。区間を検索するコードは、途中のチェックを除いて同様です。
// このノードをチェックしますif ( n . getKey (). overlapsWith ( i )) result . add ( n . getKey ());overlapsWith()は次のように定義されます。
public boolean overlapsWith ( Interval other ) { return start.compareTo ( other.getEnd ( ) ) < = 0 && end.compareTo ( other.getStart ( ) ) > = 0 ; }拡張木は、木の各レベルで次元を順に処理することで、より高次元に拡張できます。例えば、2次元の場合、木の奇数レベルにはx座標の範囲が、偶数レベルにはy座標の範囲が含まれる可能性があります。このアプローチは、データ構造を拡張二分木から拡張kd木に効果的に変換するため、挿入と削除のバランス調整アルゴリズムを著しく複雑化させます。
より簡単な解決策は、ネストされた区間木を使用することです。まず、 y座標の範囲を使用して木を作成します。次に、木の各ノードについて、y範囲がそのノードのy範囲と同じであるすべての要素に対して、x範囲に基づいて別の区間木を追加します。
このソリューションの利点は、同じコードベースを使用して任意の次元数に拡張できる点です。
最初は、入れ子構造のツリーによる追加コストが高額に思えるかもしれませんが、実際にはそうではありません。前述の入れ子構造ではない解法と同様に、x座標ごとに1つのノードが必要となるため、どちらの解法でもノード数は同じになります。唯一の追加オーバーヘッドは、垂直方向の区間ごとに1つ存在する入れ子構造のオーバーヘッドです。この構造は通常、ルートノードへのポインタと、場合によってはノード数およびツリーの深さのみで構成されるため、サイズはごくわずかです。
メディアル指向ツリー(長さ指向ツリー)は、拡張ツリーに似ていますが、対称性を持ち、二分探索木は区間のメディアルポイントで順序付けられます。各ノードには、区間の長さ(またはその半分)で順序付けられた最大値指向二分ヒープがあります。また、各ノードには部分木の最小値と最大値が格納されます(これが対称性の原因です)。
2つの区間の開始値と終了値のみを使用する、 のためにオーバーラップテストは次のように実行できます。
そして
これは、和と差を用いて簡略化できます。
これにより、重複テストは以下のように簡略化されます。
ツリーに新しい区間を追加する方法は、中央値をキーとして使用する二分探索木の場合と同じです。ノードに関連付けられたバイナリヒープにデータを格納し、上位のすべてのノードに関連付けられた最小値と最大値を更新します。
使用しましょうクエリ間隔の場合、ノードのキーの場合((間隔の)
ルートノードから始めて、各ノードにおいて、まず、クエリ区間がノードの最小値と最大値を使用してノードのサブツリーと重複する可能性があるかどうかを確認します(重複しない場合は、このノードの処理は続行しません)。
次に計算しますこのノード内の区間(子ノードではない)がクエリ区間と重なるように():
そして、そのバイナリヒープに対してクエリを実行します。より大きい
次に、ノードの左の子と右の子の両方を同様に処理します。
最悪の場合、二分探索木のすべてのノードをスキャンする必要がありますが、二分ヒープクエリが最適であるため、これは許容範囲内です(2次元の問題は両方の次元で最適になることはできません)。
このアルゴリズムは、検索操作において従来の区間木(拡張木)よりも高速であることが期待されます。要素の追加は実際には若干遅くなりますが、成長の順序は同じです。