| リンク/カットツリー | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| タイプ | 木 | |||||||||||||||
| 発明した | 1982 | |||||||||||||||
| 発明者 | ||||||||||||||||
| ビッグオー記法による時間計算量 | ||||||||||||||||
| ||||||||||||||||
リンク/カットツリーは、根付きツリーの集合であるフォレストを表すデータ構造であり、以下の操作を提供します。
表現対象のフォレストは非常に深いツリーで構成されている可能性があるため、フォレストを親ポインタツリーの単純な集合として表現すると、特定のノードのルートを見つけるのに時間がかかる場合があります。しかし、フォレスト内の各ツリーをリンク/カットツリーとして表現すると、要素がどのツリーに属するかをO ( log ( n ))の償却時間で見つけることができます。さらに、リンク/カットツリーの集合を、表現対象のフォレストの変化に合わせて迅速に調整できます。特に、マージ(リンク)とスプリット(カット)の調整をO (log( n ))の償却時間で行うことができます。
リンク/カットツリーは、表現されたフォレスト内の各ツリーを頂点が互いに素なパスに分割します。各パスは補助データ構造(多くの場合、スプレーツリーですが、元の論文はスプレーツリーより前に書かれたため、バイアス付き二分探索木を使用しています)で表現されます。補助データ構造内のノードは、対応する表現されたツリー内での深さによって順序付けられます。バリエーションの1つであるナイーブパーティショニングでは、パスはタンゴツリーと同様に、最近アクセスされたパスとノードによって決定されます。サイズによるパーティショニングでは、パスは指定されたノードの最も重い子(最も多くの子を持つ子)によって決定されます。これにより、より複雑な構造になりますが、操作のコストは償却O(log n)から最悪の場合O(log n)に削減されます。さまざまなネットワークフロー問題の解決やデータセットの統合に使用されます。
各ノードが任意の次数の順序付けされていないノードを持つ木を取り上げ、それをパスに分割します。これを表現木と呼びます。これらのパスは、補助木(ここではスプレー木を使用します)によって内部的に表現され、左から右へのノードは、ルートからパス上の最後のノードまでのパスを表します。表現木内で接続されているノードのうち、同じ優先パス上にない(したがって同じ補助木内にない)ノードは、パス親ポインタを介して接続されます。このポインタは、パスを表す補助木のルートに格納されます。

表現されたツリー上でノードvへのアクセスが行われると、そのアクセス経路が優先経路となります。ノードの優先子は、アクセス経路上の最後の子ノードです。ただし、最後のアクセスがvに対して行われた場合、またはこの特定のツリーのブランチへのアクセスが行われなかった場合は null となります。優先エッジは、優先子とvを接続するエッジです。
別のバージョンでは、最も体重の重い子供によって優先経路が決定される。

我々が関心を持つ操作はFindRoot(Node v)、、、、およびですCut(Node v)。すべての操作はサブルーチンを使用して実装されます。頂点vにアクセスすると、表現されたツリーの優先パスが、表現されたツリーのルートRからノードvへのパスに変更されます。アクセス パス上のノードが以前に優先子uを持っていた場合、パスが子wに向かうようになったら、古い優先エッジ は削除され (パス親ポインタに変更されます)、新しいパスはwを通ります。Link(Node v, Node w)Path(Node v)Access(Node v)
ノードvへのアクセスを実行すると、v には優先子ノードがなくなり、パスの末尾になります。補助ツリーのノードは深さによってキー付けされているため、補助ツリー内のvの右側にあるノードはすべて切断する必要があります。スプレーツリーでは、これは比較的簡単な手順です。v でスプレーすると、vは補助ツリーのルートになります。次に、vの右サブツリー、つまり前の優先パスでvより下にあったすべてのノードを切断します。切断されたツリーのルートにはパス親ポインタがあり、これをvに向けます。
次に、表現されたツリーをルートRまでたどり、必要に応じて優先パスを切断およびリセットします。これを行うには、vからパス親ポインタをたどります( vがルートになったので、パス親ポインタに直接アクセスできます)。v があるパスに既にルート R が含まれている場合(ノードは深さでキー付けされているため、補助ツリーの左端のノードになります)、パス親ポインタは null になり、アクセスは完了です。そうでない場合は、ポインタをたどって別のパスw上のノードに移動します。wの古い優先パスを切断し、 vがあるパスに再接続します。これを行うには、 wでスプレッドし、その右サブツリーを切り離し、パス親ポインタをwに設定します。すべてのノードは深さでキー付けされており、vのパスのすべてのノードはwのパスのすべてのノードよりも深いため(表現されたツリーではwの子であるため)、 vのツリーをwの右の子として接続します。再びvでスプレッド操作を行うと、vはルートwの子であるため、 vは単純にルートに回転します。vのパス親ポインタがnullになるまでこのプロセス全体を繰り返します。この時点で、vは表現されたツリーRのルートと同じ優先パス上にあります。

FindRoot は、ノードvを含む表現されたツリーのルートを見つけることを指します。アクセスサブルーチンはv を優先パスに配置するため、まずアクセスを実行します。これで、ノードvはルートRと同じ優先パス、つまり同じ補助ツリー上にあります。補助ツリーは深さによってキー付けされているため、ルートRは補助ツリーの最も左のノードになります。したがって、vの左の子を再帰的に選択して、それ以上進めなくなるまで繰り返します。このノードがルートRです。ルートは線形深度になる可能性があるため (これはスプレッド ツリーの最悪のケースです)、次のアクセスが速くなるようにスプレッドします。
ここでは、表現されたツリーをノードvで切断します。まず、 vにアクセスします。これにより、表現されたツリーでvより下位にあるすべての要素が、補助ツリーでvの右の子になります。vの左サブツリーにあるすべての要素は、表現されたツリーでvより上位にあるノードです。したがって、 vの左の子を切断します(この左の子は、パス親ポインタを介して元の表現されたツリーへの接続を維持しています)。これで、vは表現されたツリーのルートになります。v にアクセスすると、vより下位の優先パスも切断されますが、そのサブツリーはパス親ポインタを介してvとの接続を維持します。
vが木のルートで、wが別の木の頂点である場合、 vとw を含む木を、エッジ (v, w) を追加してリンクし、w をvの親にします。これを行うには、それぞれの木でvとwにアクセスし、w をvの左の子にします。vはルートであり、補助木ではノードが深さでキー付けされているため、 vにアクセスすると、 v は補助木に左の子を持たないことになります(ルートであるため、深さが最小になります)。wを左の子として追加すると、表現された木で w がvの親になります。
この操作では、ルートRからノードvまでのパス上のすべてのノード (またはエッジ) に対して、何らかの集計関数(「合計」、「最小値」、「最大値」、「増加」など) を実行します。これを行うには、vにアクセスします。v は、ルートRからノードvまでのパス上のすべてのノードを含む補助ツリーを提供します。このデータ構造には、最小値や最大値、サブツリー内のコストの合計など、取得したいデータを追加できます。これらのデータは、指定されたパスから定数時間で取得できます。
Switch-Preferred-Child(x, y): (right(x) が null でない場合) path-parent(right(x)) = x right(x) = y if (y が null でない場合) 親(y) = x アクセス(v): 広げる(動詞) Switch-Preferred-Child(v, null) (path-parent(v) が null でない場合) w = path-parent(v) splay(w) Switch-Preferred-Child(w, v) アクセス(v) リンク(v, w): アクセス(v) アクセス(w) left(v) = w 親(w) = v カット(v): アクセス(v) if (left(v) is not null) path-parent(left(v)) = path-parent(v) left(v) = null パス親(v) = null カットとリンクのコストはO (1) であり、アクセスのコストが加算されます。FindRoot の償却上限はO (log n ) であり、アクセスのコストが加算されます。実装によっては、データ構造に(サブツリー内の最小値または最大値を持つノード、あるいは合計値など)追加情報を追加できます。したがって、Path はアクセスの上限に加えて定数時間でこの情報を返すことができます。
したがって、実行時間を求めるには、アクセス範囲を制限する必要がある。
Accessはスプレッドを利用しますが、スプレッドにはO (log n )の償却上限があることがわかっています。したがって、残りの分析では、スプレッドを実行する必要がある回数を扱います。これは、ツリーをたどっていく際に、優先子ノードが変更される回数(優先パスで変更されるエッジの数)に等しくなります。
我々は、ヘビーライト分解と呼ばれる手法を用いてアクセスを制限した。
この手法では、部分木内のノード数に応じて、エッジを「重い」または「軽い」と分類します。 は、表現されたツリーにおけるvのサブツリー内のノード数を表します。エッジは、size(v) > 1 ⁄ 2 size(parent(v)) の場合、ヘビーエッジと呼ばれます。したがって、各ノードは最大で 1 つのヘビーエッジを持つことができます。ヘビーエッジではないエッジは、ライトエッジと呼ばれます。
ライトデプスとは、ルートから頂点vまでの特定のパス上のライトエッジの数を指します。ライトデプスは lg n以下です。これは、ライトエッジをたどるたびにノード数が少なくとも 2 分の 1 に減少するためです (親ノードの最大半分のノード数しか持つことができないため)。
したがって、表現されたツリー内の特定のエッジは、重度に優先される、重度に優先されない、軽度に優先される、軽度に優先されない、の4つの可能性のいずれかになります。
まず、上限値。
アクセスのスプレッド操作により log nが得られるので、 O (log 2 n ) の上限を証明するには、アクセスの回数を log nに制限する必要があります。
優先エッジが変化するたびに、新しい優先エッジが形成されます。そこで、形成された優先エッジの数を数えます。任意のパス上では、明るいエッジは最大で log n個なので、明るいエッジが優先エッジに変化する数も最大で log n個になります。
重エッジが好まれるようになる数は、任意の操作に対して、しかしそれは償却。一連の実行において、 n - 1 個の重いエッジが優先される可能性があります (表現されたツリーには合計で最大n - 1 個の重いエッジがあるため)。しかし、それ以降、優先される重いエッジの数は、前のステップで優先されなくなった重いエッジの数に等しくなります。優先されなくなった重いエッジごとに、軽いエッジが優先されなければなりません。優先される可能性のある軽いエッジの数は最大で log nであることは既に確認済みです。したがって、 m 回の操作で優先される重いエッジの数は です。。十分な回数の操作( ) 平均すると .
優先子変更の数を制限しました。したがって、優先子の変更ごとに償却コストが O(1) であることを示すことができれば、アクセス操作を次のように制限できます。これはポテンシャル法を用いて行われます。
補助木の木において、vの下にあるノードの数を s(v) とする。すると、ポテンシャル関数はスプレッドの償却コストは、以下の制約を受けることがわかっています。
展開後、vはそのパス親ノードwの子であることがわかっています。したがって、次のことがわかります。
この不等式とアクセス費用の償却を用いて、以下の制約条件を満たすテレスコープ式の合計値を求めます。
ここで、Rは表現されたツリーのルートであり、優先される子の変更数は であることがわかります。 . s ( R ) = nなので、次のようになります。償却済み。
リンク/カットツリーは、非巡回グラフの動的な接続性問題を解決するために使用できます。2つのノードxとyが与えられた場合、それらはFindRoot(x) = FindRoot(y)である場合に限り接続されます。同じ目的で使用できる別のデータ構造として、オイラーツアーツリーがあります。
最大フロー問題を解く際に、リンク/カットツリーを使用して、Dinicのアルゴリズムの実行時間を改善できます。に。