6

私は、ランダム アクセス (または少なくとも O(n) よりも優れている) を備えたコンテナーを設計しなければならないアプリケーションに直面しています。 ) 挿入時に指定します。

たとえば、次の配列があるとします。

[2, 9, 10, 3, 4, 6]

インデックス 2 で remove を呼び出して10 を削除できます。また、インデックス 1 で insert を呼び出して13 を挿入することもできます。

これらの 2 つの操作の後、次のようになります。

[2, 13, 9, 3, 4, 6]

番号は順番に格納され、挿入/削除操作には、番号を挿入する場所または削除する番号を指定するためのインデックス パラメータが必要です。

私の質問は、Linked List とベクトル以外に、どのような種類のデータ構造がこのようなものを維持できるかということです。次に利用可能なインデックスを優先するヒープに傾いています。しかし、私はフュージョン ツリーが有用であることについて何かを見てきました (ただし、より理論的な意味で)。

メモリ消費を抑えながら、最適な実行時間を実現できるデータ構造はどのようなものでしょうか? ハッシュテーブルを保持する広告掲載順で遊んでいますが、これまでのところ成功していません。


std:: ベクトルをまっすぐに使用して放り投げる理由は、これらの基本的な操作に関してベクトルを実行するものを作成する必要があるためです。コンテナーのサイズは、数十万の要素に成長する可能性があるため、std::vector でシフトにコミットすることは問題外です。リンクされたリスト(二重にリンクされている場合でも)と同じ問題行があり、それを特定のインデックスにトラバースすると、最悪の場合 O (n/2) になり、O (n) に丸められます。

私は、Head、Tail、および Middle ポインターを含む 2 倍のリンク リストを考えていましたが、あまり良くないと感じました。

4

2 に答える 2

7

基本的な使い方として、任意の位置で挿入・削除できるようにするために、連結リストを利用できます。O(1) の挿入/削除が可能ですが、挿入するリスト内の位置を既に見つけている場合に限ります。「特定の要素の後に」(つまり、要素へのポインターを指定して) 挿入することはできますが、「特定のインデックスに」挿入することは効率的ではありません。

インデックスを指定して要素を挿入および削除できるようにするには、より高度なデータ構造が必要になります。私が知っている少なくとも 2 つのそのような構造が存在します。

1 つはロープ構造で、一部の C++ 拡張機能 ( SGI STL、または GCC via #include <ext/rope>) で使用できます。任意の位置で O(log N) の挿入/削除が可能です。

O(log N) の挿入/削除を可能にする別の構造は、暗黙的な Treap (別名、暗黙的なデカルト ツリー) です。 http://codeforces.com/blog/entry/3767でいくつかの情報を見つけることができます。暗黙的なキーまたはhttps を使用した Treap: //codereview.stackexchange.com/questions/70456/treap-with-implicit-keys .

暗黙の treap を変更して、最小値を見つけられるようにすることもできます (また、より多くの操作をサポートすることもできます)。ロープがこれを処理できるかどうかはわかりません。

UPD : 実際、O(log N) バイナリ検索ツリー (AVL や赤黒ツリーなど) を「暗黙のキー」スキームに変換することで、リクエストに適応させることができると思います。大まかな概要は以下の通りです。

任意の時点で、連続する数 1、2、...、N をキーとして格納する二分探索木を想像してみてください (N はツリー内のノードの数です)。ツリーを変更する (ノードを挿入または削除する) たびに、保存されているすべてのキーを再計算して、1 から N の新しい値になるようにします。これにより、任意の位置での挿入/削除が可能になります。 、しかし、すべてのキーの更新には時間がかかりすぎます。

これを避けるために、ツリーにキーを明示的に格納しません。代わりに、ノードごとに、そのサブツリーにノードの数を格納します。その結果、ツリーのルートから下に移動するたびに、現在のノードのインデックス (位置) を追跡できます。必要なのは、左側にあるサブツリーのサイズを合計することだけです。これにより、 k を指定すると、インデックスk を持つノードを見つけることができます(つまり、二分探索木の標準順序で k 番目)、O(log N) 時間。この後、標準のバイナリ ツリー手順を使用して、この位置で挿入または削除を実行できます。更新中に変更されたすべてのノードのサブツリー サイズを更新するだけで済みますが、これは変更されたノードごとに O(1) 時間で簡単に実行できるため、挿入または削除の合計時間は次のように O(log N) になります。元の二分探索木。

したがって、このアプローチでは、任意の O(log N) 二分探索木をベースとして使用して、O(log N) 時間で特定の位置にノードを挿入/削除/アクセスできます。もちろん、必要な追加情報 (「値」) をノードに格納することもできます。また、各ノードのサブツリーの最小値を保持するだけで、ツリー内のこれらの値の最小値を計算することもできます。

ただし、前述の Treap とロープは、分割とマージの操作 (部分文字列/部分配列を取得し、2 つの文字列/配列を連結する) もできるため、より高度です。

于 2015-04-23T07:58:37.860 に答える
0

「インデックス可能な」バリエーションで線形時間ランク操作を実装できるスキップ リストを考えてみましょう。

アルゴリズム (疑似コード) については、Pugh による A Skip List Cookbookを参照してください。

上記の@Petrによって概説された「暗黙のキー」バイナリ検索ツリーメソッドは、より簡単に取得でき、さらにパフォーマンスが向上する可能性があります。

于 2016-05-02T19:40:49.650 に答える