ジョーの答えは非常に優れており、重要なキーワードをすべて提供します。
簡潔なデータ構造の研究はまだ初期段階であり、結果の多くは大部分が理論的なものであることを認識しておく必要があります。提案されたデータ構造の多くは実装が非常に複雑ですが、ほとんどの複雑さは、ユニバースサイズと格納されている要素の数の両方で漸近的な複雑さを維持する必要があるという事実によるものです。これらのいずれかが比較的一定であれば、複雑さの多くはなくなります。
コレクションが半静的である場合(つまり、挿入がまれであるか、少なくともボリュームが少ない場合)、更新と組み合わせて、実装が簡単な静的データ構造(Sadakaneのsdarrayが最適です)を検討する価値があります。キャッシュ。基本的に、更新を従来のデータ構造(Bツリー、トライ、ハッシュテーブルなど)に記録し、定期的に「メイン」のデータ構造を一括更新します。転置インデックスは検索に多くの利点がありますが、インプレースで更新することが難しいため、これは情報検索で非常に人気のある手法です。このような場合は、コメントでお知らせください。この回答を修正して、いくつかの指針を示します。
挿入の頻度が高い場合は、簡潔なハッシュをお勧めします。基本的な考え方はここで説明するのに十分単純なので、説明します。
したがって、基本的な情報理論上の結果は、アイテムのユニバースから要素を格納し、他の情報がない場合(たとえば、要素間に相関関係がない場合)、それを格納するビット。(特に指定のない限り、すべての対数は底が2です。)これだけ多くのビットが必要です。それを回避する方法はありません。のunulog(un)+O(1)
今いくつかの用語:
- データを格納でき、ビットのスペースでの操作をサポートできるデータ構造がある場合、これを暗黙的なデータ構造と呼びます。log(un)+O(1)
- あなたがにデータを格納し、あなたの業務を支援することができるデータ構造を持っている場合はビットのスペースを。これをコンパクトデータ構造と呼びます。実際には、これは相対的なオーバーヘッド(理論上の最小値に対する)が一定の範囲内にあることを意味します。5%のオーバーヘッド、10%のオーバーヘッド、または10倍のオーバーヘッドの可能性があります。log(un)+O(log(un))=(1+O(1))log(un)
- あなたがあなたの業務をデータを格納し、サポートすることができ、データ構造がある場合はビットのスペースを。これを簡潔なデータ構造と呼びます。log(un)+o(log(un))=(1+o(1))log(un)
簡潔とコンパクトの違いは、リトルオーとビッグオーの違いです。絶対値のものをしばらく無視しています...
- g(n)=O(f(n))は、すべての、ような定数と数値が存在することを意味します。cn0n>n0g(n)<c⋅f(n)
- g(n)=o(f(n))は、すべての定数に対して、すべてのに対してようなが存在することを意味します。cn0n>n0g(n)<c⋅f(n)
非公式には、big-ohとlittle-ohはどちらも「定数係数内」ですが、big-ohでは定数が(アルゴリズムデザイナー、CPUメーカー、物理法則などによって)選択されますが、 -ああ、定数を自分で選ぶと、好きなだけ小さくすることができます。言い換えると、簡潔なデータ構造では、問題のサイズが大きくなると、相対的なオーバーヘッドが任意に小さくなります。
もちろん、問題のサイズは、必要な相対的なオーバーヘッドを実現するために巨大化する必要があるかもしれませんが、すべてを持つことはできません。
わかりました、それを私たちのベルトの下で、問題にいくつかの数字を付けましょう。キーがビットの整数(つまり、ユニバースサイズが)であり、これらの整数のを格納するとします。完全に占有され、無駄のない理想化されたハッシュテーブルを魔法のように配置できるため、正確にハッシュスロットが必要だとします。n2n2m2m
ルックアップ操作では、ビットのキーをハッシュし、ビットをマスクしてハッシュスロットを見つけ、テーブルの値がキーと一致したかどうかを確認します。ここまでは順調ですね。nm
このようなハッシュテーブルは、ビットを使用します。これより上手くできますか?n2m
ハッシュ関数が可逆であると仮定します。その後、各ハッシュスロットにキー全体を格納する必要はありません。ハッシュスロットの場所からビットのハッシュ値が得られるので、残りのビットのみを保存した場合は、これらの2つの情報(ハッシュスロットの場所とそこに保存されている値)からキーを再構築できます。したがって、必要なのはビットのストレージだけです。hmn−m(n−m)2m
場合はに比べて小さく、、スターリングの近似と少しの算術(証明は運動です!)ことを明らかに:2m2n
(n−m)2m=log(2n2m)+o(log(2n2m))
したがって、このデータ構造は簡潔です。
ただし、2つの問題があります。
最初の問題は、「良い」可逆ハッシュ関数を構築することです。幸い、これは見た目よりもはるかに簡単です。暗号学者はいつでも可逆関数を作成し、それらを「暗号」と呼びます。たとえば、ハッシュ関数をFeistelネットワークに基づくことができます。これは、非可逆ハッシュ関数から可逆ハッシュ関数を構築する簡単な方法です。
2番目の問題は、誕生日のパラドックスのおかげで、実際のハッシュテーブルは理想的ではないということです。したがって、こぼれることなく完全な占有率に近づける、より洗練されたタイプのハッシュテーブルを使用する必要があります。カッコウハッシュは、理論的には理想に近づき、実際には非常に近いため、これに最適です。
カッコウハッシュには複数のハッシュ関数が必要であり、ハッシュスロットの値には、使用されたハッシュ関数でタグ付けする必要があります。したがって、たとえば4つのハッシュ関数を使用する場合、各ハッシュスロットにさらに2ビットを格納する必要があります。これは、が大きくなってもまだ簡潔であるため、実際には問題ではなく、キー全体を格納するよりも優れています。m
ああ、あなたはファン・エムデ・ボアスの木を見ることもできます。
もっと考える
がどこかにある場合、はおおよそなので、値の間に相関関係がないと仮定すると、基本的には何もできませんビットベクトルよりも優れています。上記のハッシュソリューションは事実上その場合に縮退します(ハッシュスロットごとに1ビットを格納することになります)が、ハッシュ関数を使用するよりも、キーをアドレスとして使用する方が安上がりです。nu2log(un)u
がに非常に近い場合、すべての簡潔なデータ構造の文献では、辞書の意味を逆にするように勧めています。セットに出現しない値を格納します。ただし、今は事実上、削除操作をサポートする必要があり、簡潔な動作を維持するには、さらに要素が「追加」されるときにデータ構造を縮小できる必要があります。ハッシュテーブルの拡張はよく理解されている操作ですが、縮小はそうではありません。nu