順序付けで他の2つの要素の「間に」要素を挿入できる効率的な順序付けを維持していますか?


8

次のような要素の束に注文があると想像してください:

ここに画像の説明を入力してください

矢印は意味します。また、推移的です:。XYX<Y(X<Y)(Y<Z)(X<Z)

ようなクエリに効率的に応答するには、何らかのラベル付けまたはデータ構造が必要です。たとえば、左から右へのノードに番号を付けることができ、従って、単にクエリに答えるために比較整数行うことができます:{}?A \ stackrel {<} D \ 1 <4を意味\ Tを暗示。次のようになります。A<?DA<?D1<4T

ここに画像の説明を入力してください

番号は順序であり、文字は単なる名前です。

しかし、次のように、順序付けで他の2つの要素の「間に」要素を挿入する必要がある場合はどうでしょう。

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

どのようにしてそのような順序を維持できますか?単純な番号付けでは、使用する整数の間に「2,3」がないという問題に遭遇2,3します。

回答:


7

これは「注文のメンテナンス」の問題として知られています。クエリと挿入の両方に償却時間を使用する比較的単純なソリューションがあります。さて、「比較的単純」とは、いくつかの構成要素を理解する必要があることを意味しますが、それらを取得すると、残りの部分を見るのは難しくありません。O(1)

http://courses.csail.mit.edu/6.851/spring12/lectures/L08.html

基本的な考え方は、2レベルのデータ構造です。トップレベルはRealz SlawによるAVLツリーソリューションに似ていますが、

  • ノードは、ツリー内の順序と一致する順序で、長さビット文字列で直接ラベル付けされます。したがって、比較には一定の時間がかかりますOlg

  • スケープゴートツリーやウェイトバランスツリーのように、AVLツリーよりも回転が少ないツリーが使用されるため、再ラベル付けの頻度は少なくなります。

最下層は木の葉です。そのレベルは、ラベルの同じ長さを使用して、のみ保持簡単にリンクされたリスト内の各リーフのアイテムを。これにより、積極的にラベルを付け直すのに十分なビットが追加されます。OlgOlg

挿入ごとに葉が大きくなりすぎたり、小さくなりすぎたりすると、最上位のレベルに変化が生じ、償却時間(最悪の場合の時間)がかかります。償却後、これはのみです。OlgOlgΩO1

最悪の場合の時間で更新を実行するためのはるかに複雑な構造が存在します。O1


7

単純な番号付けの代わりに、CPU整数の整数の最小値と最大値など、大きな(一定のサイズの)範囲に数値を分散させることができます。次に、周囲の2つの数値を平均することで、「間に」数値を挿入し続けることができます。数値が密集しすぎた場合(たとえば、2つの隣接する整数があり、その間に数値がない場合)、順序全体を一度だけ再番号付けして、範囲全体に均等に数値を再配分できます。

もちろん、大きな定数の範囲内のすべての数値が使用されるという制限に遭遇する可能性があります。まず、これは通常は問題ではありません。マシンのinteger-sizeが十分に大きいため、より多くの要素がある場合は、とにかくメモリに収まらない可能性があります。しかし、それが問題である場合は、より大きな整数の範囲で番号を付け直すことができます。

入力順序が病理的でない場合、このメソッドは再番号付けを償却する可能性があります。

質問に答える

単純な整数比較は、クエリ答えることができます。バツ<Y

単純な整数比較なので、マシン整数を使用すると、クエリ時間は非常に速くなります()。より大きな範囲を使用すると、より大きな整数が必要になり、比較にはがかかり。O1Oログ|teger|

挿入

まず、質問に示されている順序のリンクリストを維持します。ここに挿入する場合、新しい要素を間に配置するノードを指定すると、ます。O1

新しい要素のラベル付けは、周囲の数値を平均することで新しい分子を簡単に計算できるため、通常はです。時々、「間に」数値が足りなくなり、時間の再番号付け手順がトリガーされることがあります。O1O

番号の付け直しを避ける

整数の代わりに浮動小数点数を使用できるため、2つの「隣接する」整数を取得すると、それら平均化できます。したがって、2つの整数floatに直面したときに番号の付け直しを回避できます。それらを半分に分割するだけです。ただし、最終的には浮動小数点型の精度が不足し、2つの「隣接する」浮動小数点数を平均化できなくなります(周囲の数値の平均は、周囲の数値の1つとおそらく同じになります)。

同様に、「小数点位置」整数を使用できます。この場合、要素に2つの整数を保持します。1つは数値用、もう1つは小数用です。これにより、番号の付け直しを回避できます。ただし、10進整数は最終的にオーバーフローします。

各ラベルに整数またはビットのリストを使用すると、番号の付け直しを完全に回避できます。これは基本的に、長さが無制限の10進数を使用することと同じです。比較は辞書式に行われ、比較時間は関係するリストの長さまで増加します。ただし、これによりラベル付けのバランスが崩れる可能性があります。一部のラベルには整数が1つしか必要ない(小数ではない)場合もあれば、長いリスト(長い小数)を持つ場合もあります。これは問題であり、ここでも番号付け(ここでは番号のリスト)を選択した範囲(ここで範囲とはリストの長さを意味する)に均等に再配布することで、番号付けが役立つ場合があります。 。


このメソッドは、実際にはこのアルゴリズム実装関連するデータ構造)で使用されています。アルゴリズムの過程では、任意の順序を維持する必要があり、著者はこれを達成するために整数と再番号付けを使用します。


数字に固執しようとすると、キースペースが多少制限されます。代わりに、比較ロジック "a" <"ab" <"b"を使用して、可変長文字列を使用できます。まだ解決すべき2つの問題が残っていますA.キーが任意に長くなる可能性がありますB.長いキーの比較はコストがかかる可能性があります


3

キーのないAVLツリーなどを維持できます。

これは次のように機能します。ツリーは、通常のAVLツリーと同じようにノードの順序を維持しますが、キーがノードの「あるべき場所」を決定するのではなく、キーがないので、「後にノードを明示的に挿入する必要があります。 「別のノード(つまり、2つのノードの「間にある」)。「後」とは、ツリーの順序どおりのトラバーサルで後に来ることを意味します。したがって、ツリーは自然に順序付けを維持し、AVLの組み込みローテーションにより、バランスがとれます。これにより、すべてが自動的に均等に分散されます。

挿入

質問に示されているように、リストへの定期的な挿入に加えて、個別のAVLツリーを維持します。「before」ノードと「after」ノードがあるため、リスト自体への挿入はです。O(1)

ツリーへの挿入時間はであり、AVLツリーへの挿入と同じです。挿入には、後に挿入するノードへの参照が含まれ、新しいノードを右の子の左端のノードの左に挿入するだけです。この場所は、ツリーの順序で「次」です(順序トラバーサルの次です)。次に、典型的なAVLローテーションを実行して、ツリーのバランスを再調整します。「前に挿入」についても同様の操作を実行できます。これは、リストの先頭に何かを挿入する必要があり、ノードの「前」のノードがない場合に役立ちます。O(logn)

質問に答える

クエリに答えるには、ツリー内でとすべての祖先を見つけ、ツリー内で祖先が分岐する場所の場所を分析します。「左」に分岐するのは、2つのうち小さい方です。(X<?Y)XY

この手順では、時間をかけて、ツリーをルートに登り、祖先リストを取得します。これは整数の比較よりも遅く見えるのは事実ですが、実際には同じです。CPUでのその整数比較だけが、になるように大きな定数によって制限されます。この定数をオーバーフローさせる場合、複数の整数( integers実際には)を維持し、同じ比較。あるいは、ツリーの高さを一定量「拘束」し、マシンが整数で行うのと同じ方法で「チート」することができます。クエリはように見えます。O(logn)O(1)O(logn)O(logn)O(1)

挿入操作デモ

実例を示すために、質問のリストからの順序でいくつかの要素を挿入できます。

ステップ1

始めるD

リスト:

リストステップ1

木:

ツリーステップ1

ステップ2

、挿入します。C<C<D

リスト:

リストステップ2

木:

ツリーステップ2

「前」に明示的に配置したことに注意してくださいという文字がDの前にあるからではなく、リスト内でだからです。CDC<D

ステップ3

、挿入します。A<A<C

リスト:

リストステップ3

木:

回転前のツリーステップ3

AVLローテーション:

回転後のツリーステップ3

ステップ4

、挿入します。BA<B<C

リスト:

リストステップ4

木:

ツリーステップ4

ローテーションは必要ありません。

手順5

挿入、ED<E<

リスト:

リストステップ5

木:

ツリーステップ5

手順6

挿入FB<F<C

BBFB

リスト:

リストステップ6

木:

回転前のツリーステップ6

AVLローテーション:

回転後のツリーステップ6

比較動作デモ

A<?F

ancestors(A) = [C,B]
ancestors(F) = [C,B]
last_common_ancestor = B
B.left = A
B.right = F
... A < F #left is less than right

D<?F

ancestors(D) = [C]
ancestors(F) = [C,B]
last_common_ancestor = C
C.left = D
C.right = B #next ancestor for F is to the right
... D < F #left is less than right

B<?A

ancestors(B) = [C]
ancestors(A) = [B,C]
last_common_ancestor = B
B.left = A
... A < B #left is always less than parent

グラフのソース


@saadtaameが修正され、下部にドットファイルソースが追加されました。これを指摘してくれてありがとう。
Realz Slaw 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.