あまり知られていないが有用なデータ構造は何ですか?


795

非常に便利なデータ構造がいくつかありますが、ほとんどのプログラマーには知られていません。それらはどれですか?

リンクリスト、バイナリツリー、ハッシュについては誰もが知っていますが、たとえばスキップリストブルームフィルターについてどうでしょう。あまり一般的ではありませんが、優れたアイデアに依存し、プログラマーのツールボックスを充実させるため、知っておく価値のあるデータ構造をもっと知りたいと思います。

PS:また、一般的なデータ構造のプロパティを巧みに利用するリンクのダンスなどの手法にも興味があります。

編集:データ構造をより詳細に説明するページへのリンク含めるようにしてください。また、(JonasKölkerがすでに指摘しているように)データ構造が優れている理由についていくつかの単語を追加してみてください。また、回答ごとに1つのデータ構造を提供するようにしてください。これにより、投票だけに基づいて、より優れたデータ構造が上位に浮かぶようになります。


回答:


271

試行錯誤(プレフィックスツリーまたはクリティカルビットツリーとも呼ばれます)は40年以上前から存在していますが、まだあまり知られていません。トライの非常に優れた使い方については、「TRASH-動的LCトライとハッシュデータ構造」で説明しています。トライでは、ハッシュ関数と組み合わせます。


12
スペルチェッカーで非常に一般的に使用されています
Steven A. Lowe

バースト試行も興味深いバリアントであり、文字列のプレフィックスのみをノードとして使用し、それ以外の場合は文字列のリストをノードに格納します。
Torsten Marek、

Perl 5.10の正規表現エンジンは自動的に試行を作成します。
ブラッド・ギルバート、

私の経験では、ポインタは一般的にcharよりも長いので、試行錯誤は高価です。これらは特定のデータセットにのみ適しています。
Joe

18
トピックに関係なく、誰もがjQueryに言及せずにSOの質問は完了しません。改訂されたjavascript-dictionary-search
Oskar Austegard

231

ブルームフィルターmビットのビット配列。最初はすべて0に設定されています。

項目を追加するには、配列にk個のインデックスを与えるk個のハッシュ関数を使用して、1に設定します。

アイテムがセット内にあるかどうかを確認するには、kインデックスを計算し、それらがすべて1に設定されているかどうかを確認します。

もちろん、これは誤検知の可能性をある程度与えます(Wikipediaによると、約0.61 ^(m / n)で、nは挿入された項目の数です)。偽陰性は不可能です。

アイテムを削除することは不可能ですが、intおよびincrement / decrementの配列で表されるカウントブルームフィルターを実装できます。


20
辞書での使用について言及するのを忘れる:)値のないハッシュテーブルのように、完全な辞書を約512kのブルームフィルターに圧縮できます
Chris S

8
Googleは、BigTableの実装でブルームフィルターの使用を引用しています。
ブライアンジャンフォルカロ

16
@FreshCode実際には偽陽性を取得できるが、偽陰性を取得できないため、セットに要素がないことを実際に安価にテストできます
Tom Savage、

26
@FreshCode @Tom Savageが言ったように、ネガティブをチェックする場合により便利です。たとえば、高速で小さな(メモリ使用量の観点から)スペルチェッカーとして使用できます。それにすべての単語を追加してから、ユーザーが入力した単語を調べてみてください。ネガティブになった場合は、スペルミスです。次に、より高価なチェックを実行して、最も近い一致を見つけ、修正を提供できます。
lacop

5
@ abhin4v:ブルームフィルターは、ほとんどのリクエストが「いいえ」の回答を返す可能性が高い場合(この場合など)によく使用されます。つまり、少数の「はい」の回答は、より遅い正確なテストでチェックできます。これでも、平均クエリ応答時間が大幅に短縮されます。Chromeのセーフブラウジングがそれを実行するかどうかはわかりませんが、それは私の推測です。
j_random_hacker

140

ロープ:安価なプリペンド、サブストリング、中間挿入、追加を可能にする文字列です。私は実際には一度しか使用していませんでしたが、他の構造では不十分でした。通常の文字列と配列の先頭に追加することは、私たちがする必要があるものに対しては非常に高価であり、すべてを逆にすることは問題外でした。


私は自分の用途のためにこのようなことを考えていました。すでに他の場所で実装されていることを知ってうれしい。
Kibbee

15
SGI STL(1998)に実装があります:sgi.com/tech/stl/Rope.html
quark

2
:パフォーマンスが優れていた-私は最近、Javaのためにこれに非常に類似した何かを書いたと呼ばれていたされているもの知らず code.google.com/p/mikeralib/source/browse/trunk/Mikera/src/...
mikera

ロープはかなり珍しいです:stackoverflow.com/questions/1863440/...
ウィル

6
ミケラのリンクが古くなっています。こちらが現在のものです。
aptwebapps 2011年

128

スキップリストはかなりきれいです。

ウィキペディア
スキップリストは、複数の並列ソートされたリンクリストに基づく確率的データ構造であり、効率はバイナリ検索ツリーに匹敵します(ほとんどの操作の順序ログn平均時間)。

それらは、バランスのとれたツリーの代替として使用できます(バランシングの厳密な実施ではなく、確率的なバランシングを使用)。それらは、実装が簡単で、言うよりも赤黒木です。彼らはすべての優れたプログラマーツールチェストに含まれるべきだと思います。

スキップリストの詳細な紹介が必要な場合は、ここにMITのアルゴリズム入門講義のビデオへのリンクがあります。

また、ここにはスキップリストを視覚的に示すJavaアプレットがあります。


+1 Qtは、ソートされたマップとセットにRBツリーではなくスキップリストを使用します。うん、彼らは(とにかく命令型言語で)気の利いたです。
Michael Ekstrand 2010

2
Redisはスキップリストを使用して「ソートセット」を実装します。
antirez 2011年

スキップリストは、適切なデータ構造が必要で、データの順序が保証されておらず、他の "バランスのとれた"データ構造よりも単純な実装が必要な場合に使用する、おそらく私のお気に入りのデータ構造です。そのような良いこと。
earino

興味深い副注:スキップリストに十分なレベルを追加すると、基本的にBツリーになります。
Riyad Kalla

92

空間インデックス、特にRツリーKDツリーは、空間データを効率的に格納します。これらは、地理的マップ座標データやVLSI配置配線アルゴリズムに適しており、最近傍検索にも使用できます。

ビット配列は個々のビットをコンパクトに格納し、高速ビット操作を可能にします。


6
空間インデックスは、重力のような長距離力を含むN体シミュレーションにも役立ちます。
ジャスティンピール

87

ジッパー -データ構造の派生物で、構造を変更して「カーソル」の自然な概念-現在の場所を持ちます。これらは、インデックスが範囲外にならないことを保証するため、本当に役立ちます-たとえば、 xmonadウィンドウマネージャーで、どのウィンドウがフォーカスされたかを追跡するために使用されます。

驚くべきことに、微積分から元のデータ構造のタイプにテクニックを適用することで、それらを導出できます!


2
これは関数型プログラミングでのみ役立ちます(命令型言語では、ポインタまたはインデックスを保持するだけです)。また、ジッパーが実際にどのように機能するかはまだわかりません。
Stefan Monov

4
@Stefan要点は、個別のインデックスやポインタを今すぐ保持する必要がないことです。
Don Stewart、

69

ここにいくつかあります:

  • サフィックスを試みます。ほとんどすべての種類の文字列検索に役立ちます(http://en.wikipedia.org/wiki/Suffix_trie#Functionality)。サフィックス配列も参照してください。接尾辞ツリーほど高速ではありませんが、全体的にかなり小さいです。

  • スプレイツリー(上記のとおり)。彼らがクールな理由は3つあります:

    • それらは小さいです:バイナリツリーで行うように左と右のポインターのみが必要です(ノードの色やサイズの情報を保存する必要はありません)
    • それらは(比較的)実装が非常に簡単です
    • それらは、ホスト全体の「測定基準」に最適な償却済みの複雑さを提供します(log nルックアップ時間は誰もが知っている時間です)。見るhttp://en.wikipedia.org/wiki/Splay_tree#Performance_theorems
  • ヒープ順の検索ツリー:(キーとプリオ)のペアのペアをツリーに格納します。これは、キーに関しては検索ツリーであり、優先度に関してはヒープ順です。そのような木が独特の形をしていることを示すことができます(そして、それは常に完全に左に詰め込まれているわけではありません)。ランダムな優先順位により、予想されるO(log n)検索時間IIRCが得られます。

  • ニッチなものは、O(1)ネイバークエリを使用した無向平面グラフの隣接リストです。これは、既存のデータ構造を編成する特定の方法としては、それほどデータ構造ではありません。方法は次のとおりです。すべての平面グラフには次数が最大で6のノードがあります。そのようなノードを選択し、その隣接ノードを隣接リストに配置して、グラフから削除し、グラフが空になるまで再帰します。ペア(u、v)が与えられたら、vの隣接リストでuを探し、uの隣接リストでvを探します。どちらのサイズも最大6であるため、これはO(1)です。

上記のアルゴリズムでは、uとvが隣接している場合、vのリストにuとuのリストにvの両方はありません。これが必要な場合は、各ノードの欠落しているネイバーをそのノードのネイバーリストに追加するだけで、高速ルックアップのために調べる必要があるネイバーリストの量を保存します。


ヒープ順検索ツリーはtreapと呼ばれます。これらを使用して実行できる1つのトリックは、ノードの優先度を変更して、ノードをツリーの一番下にプッシュして削除しやすくすることです。
paperhorse 2009

1
「ヒープ順検索ツリーは、treapと呼ばれます。」-私が聞いた定義では、IIRC、treapはランダムな優先順位を持つヒープ順の検索ツリーです。アプリケーションに応じて、他の優先度を選択することもできます...
JonasKölker'19年

2
接尾辞トライは、はるかにクールな接尾辞ツリーとほとんど同じですが、まったく同じではありません。これには、文字列があり、エッジに個別の文字はなく、線形時間(!)で構築できます。また、漸近的に遅いにもかかわらず、実際には、サフィックス配列は、サイズが小さく、ポインターの間接参照が少ないため、多くのタスクでサフィックスツリーよりもはるかに高速です。ところで、O(1)平面グラフルックアップが大好きです!
j_random_hacker

@j_random_hacker:サフィックス配列は漸近的に遅くなりません。以下は、線形サフィックス配列構築用の〜50行のコードです:cs.helsinki.fi/u/tpkarkka/publications/icalp03.pdf
Edward KMETT

1
@エドワード・クメット:私は実際にその論文を読んだことがありましたが、これはサフィックス配列の構築における画期的な進歩でした。(接尾辞ツリーを経由することで線形時間構築が可能であることがすでに知られていましたが、これは紛れもなく実用的な「直接」アルゴリズムでした。)しかし、LCAを除いて、構築外の一部の操作は、依然として接尾辞配列で漸近的に低速です。テーブルも造られています。これはO(n)でも実行できますが、そうすると、純粋なサフィックス配列のサイズと局所性の利点が失われます。
j_random_hacker 2010

65

標準のデータ構造のロックフリーの代替、つまりロックフリーのキュー、スタック、リストは見過ごされていると思います。
同時実行の優先度が高くなり、ミューテックスまたはロックを使用して同時読み取り/書き込みを処理するよりもはるかに立派な目標になるため、これらはますます重要になります。

ここにいくつかのリンクがあります
http://www.cl.cam.ac.uk/research/srg/netos/lock-free/
http://www.research.ibm.com/people/m/michael/podc-1996.pdf [PDFへのリンク]
http://www.boyet.com/Articles/LockfreeStack.html

Mike Actonの(しばしば挑発的な)ブログには、ロックフリーのデザインとアプローチに関する優れた記事がいくつかあります


ロックフリーの代替手段は、今日のマルチコア、非常に並行性、スケーラビリティー依存症の世界では非常に重要です:-)
earino

まあ、ほとんどの場合、かく乱者は実際にはより良い仕事をします。
deadalnix、2011年

55

一連の項目を個別のセットに分割してメンバーシップをクエリする必要がある場合、Disjoint Setはかなり便利だと思います。UnionとFindオペレーションを適切に実装すると、実質的に一定の償却コストが発生します(データ構造クラスを正しくリコールすると、アッカーマンナンの関数の逆になります)。


8
これは「union-findデータ構造」とも呼ばれます。アルゴリズムクラスでこの巧妙なデータ構造について初めて知ったとき、私は畏敬の念を抱きました...
BlueRaja-Danny Pflughoeft '28 / 01/10

union-find-delete拡張機能では、一定時間の削除も可能です。
ピーカー、2011年

4
ダンジョンジェネレーターに
ディスジョイント

52

フィボナッチヒープ

これらは、最短経路問題など、グラフに関連する多くの問題に対して、いくつかの最速の既知のアルゴリズムで(漸近的に)使用されます。ダイクストラのアルゴリズムは、標準バイナリヒープを使用してO(E log V)時間で実行されます。フィボナッチヒープを使用すると、O(E + V log V)に改善されます。これは、密なグラフの大幅な高速化です。残念ながら、しかし、それらには高い一定の要因があり、実際には実際的でないことがよくあります。


あなたが言ったように高い一定の要因、そしてしなければならなかった友人によるとうまく実装するのは難しい。最終的にはそれほどクールではありませんが、それでも、知っておく価値があります。
p4bl0 2010年

これらの人たち は、他の種類のヒープと比較して、競争力のある動作をさせました。ただし、理論的な分析は部分的に公開されています。
マヌエル

フィボナッチヒープに関する私の経験から、メモリ割り当てのコストのかかる操作は、配列によってバックエンドされる単純なバイナリヒープよりも効率が悪いことがわかりました。
2012年

44

3Dレンダリングの経験がある人は、BSPツリーに精通している必要があります。一般に、カメラの座標と方位を知ってレンダリングできるように3Dシーンを構造化する方法です。

バイナリ空間分割(BSP)は、超平面によって空間を凸集合に再帰的に分割する方法です。この細分化により、BSPツリーと呼ばれるツリーデータ構造を使用してシーンが表現されます。

つまり、複雑な形状のポリゴンを凸型のセット、または完全に非反射角度(180°未満の角度)で構成される小さなポリゴンに分割する方法です。スペース分割のより一般的な説明については、スペース分割を参照してください。

元々、このアプローチは3Dコンピュータグラフィックスでレンダリング効率を上げるために提案されました。他のアプリケーションには、CADでの形状(コンストラクションソリッドジオメトリ)による幾何学的操作の実行、ロボット工学や3Dコンピュータゲームでの衝突検出、その他の複雑な空間シーンの処理を伴うコンピュータアプリケーションなどがあります。


...関連するoctreesとkd-trees。
Lloeki、2011


38

見ていフィンガー木をあなたがのファンている場合は特に、前述した純粋に機能的なデータ構造。これらは、償却された一定時間での終わりへのアクセスをサポートする永続的なシーケンスの機能的な表現であり、小さいピースのサイズでの連結と時間の対数での分割です。

元の記事に従って:

機能的な2-3フィンガーツリーは、Okasaki(1998)によって導入された、暗黙の再帰的なスローダウンと呼ばれる一般的な設計手法の例です。これらのツリーは、暗黙の両端キュー構造の拡張であり、ペアを2〜3ノードに置き換えて、効率的な連結と分割に必要な柔軟性を提供することはすでに説明しました。

フィンガーツリーはモノイドでパラメーター化でき、異なるモノイドを使用すると、ツリーの動作が異なります。これにより、Finger Treeは他のデータ構造をシミュレートできます。



この重複する答えを見てください、それは読む価値があります!
Francois G

34

循環バッファまたはリングバッファ -特にストリーミングに使用されます。


4
また、うんざりして、なんとかしてなんとかして特許を取得することができました(少なくともビデオに使用した場合)。 ip.com/patent/USRE36801
David Eison

リンクを読むことに基づいて、データ構造自体は特許を取得しているとは思いませんが、それに基づいたいくつかの発明です。これは間違いなく使用頻度の低いデータ構造であることに同意します。
重力

33

誰もマークルツリー(つまり、ハッシュツリー)について言及していないことに驚いています。

ファイルの一部しか使用できない場合に、ファイル全体のハッシュを検証する多くの場合(P2Pプログラム、デジタル署名)で使用されます。


32

<zvrba>ヴァンエンデボアの木

それらがなぜクールなのを知ることは有用だと思います。一般的に、「なぜ」という質問が最も重要です;)

私の答えは、使用中のキーの数に関係なく、{1..n}キーを持つO(log log n)辞書を提供することです。繰り返し半分にすることでO(log n)が得られるのと同じように、繰り返しsqrtingを行うとO(log log n)が得られます。これはvEBツリーで発生することです。


彼らは理論的な観点からいいです。ただし、実際には、競争力のあるパフォーマンスを引き出すことは非常に困難です。私が知っている紙はそれらを32ビットキーまでうまく機能させました(citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.7403)アプローチはおそらく34-35ビット以上にスケーリングされませんまたはそのため、その実装はありません。
マヌエル

それらが優れているもう1つの理由は、それらが多くのキャッシュオブソリューティブアルゴリズムの主要なビルディングブロックであることです。
Edward KMETT 2010


29

ハッシュテーブルの興味深いバリアントはCuckoo Hashingと呼ばれます。ハッシュの衝突に対処するために、1つではなく複数のハッシュ関数を使用します。衝突は、プライマリハッシュで指定された場所から古いオブジェクトを削除し、代替ハッシュ関数で指定された場所に移動することで解決されます。Cuckoo Hashingを使用すると、3つのハッシュ関数だけで負荷率を最大91%まで増加させることができ、アクセス時間も良好であるため、メモリ空間をより効率的に使用できます。


5
より速いと主張されている石けり遊びハッシングを確認してください。
chmike、

27

最小-最大ヒープはのバリエーションであるヒープダブルエンドプライオリティキューを実装しています。これは、ヒーププロパティを単純に変更することで実現します。偶数(奇数)レベルのすべての要素がすべての子および孫よりも小さい(大きい)場合、ツリーは最小-最大順序であるといいます。レベルには1から番号が付けられています。

http://internet512.chonbuk.ac.kr/datastructure/heap/img/heap8.jpg


実装が難しいです。最高のプログラマでさえそれを誤解する可能性があります。
2011年

26

私はCache Obliviousデータ構造が好きです。基本的な考え方は、ツリーを再帰的に小さいブロックにレイアウトして、さまざまなサイズのキャッシュがブロックにうまく適合するブロックを利用できるようにすることです。これにより、RAMのL1キャッシュからディスクから読み取られた大きなデータチャンクまで、あらゆるキャッシングレイヤーのサイズの詳細を知る必要なしに、キャッシングを効率的に使用できます。


そのリンクからの興味深い転写:「重要なのは、1977年にPeter Van Emde Boasによって考案されたvan Emde Boasツリーデータ構造にちなんで名付けられたvan Emde Boasレイアウトです」
sergiol

23

左寄り赤黒木。2008年に公開されたRobert Sedgewickによる赤黒木の大幅に簡略化された実装(実装するコード行の半分)。赤黒ツリーの実装に頭を悩ませたことがあるなら、このバリアントについて読んでください。

アンダーソンツリーと非常に似ています(同一でない場合)。



19

GerthStøltingBrodalとChris Okasakiによるブートストラップされたスキュー二項ヒープ

それらの長い名前にもかかわらず、それらは関数設定でさえ、漸近的に最適なヒープ操作を提供します。

  • O(1)サイズ、共用体、挿入、最小
  • O(log n) deleteMin

組合がかかることに注意してくださいO(1)ではなくO(log n)、そのような一般的なデータ構造の教科書に覆われて、よりよく知られているヒープとは異なり、時間左翼ヒープフィボナッチヒープとは異なり、これらの漸近性は、永続的に使用されたとしても、償却されるのではなく、最悪の場合です!

Haskellには複数の 実装があります。

それらは、Brodalが同じ症状を示す命令ヒープを考案した後、Brodalと岡崎によって共同で導出されました。


18
  • リアルタイムレイトレーシングで(特に)使用される空間データ構造であるKd-Treesには、さまざまなスペースと交差する三角形をクリップする必要があるという欠点があります。一般に、BVHは軽量であるため高速です。
  • MX-CIF Quadtrees、通常のクワッドツリーとクワッドのエッジ上のバイナリーツリーを組み合わせることにより、任意のポイントセットの代わりにバウンディングボックスを格納します。
  • 関与する定数により、アクセス時間が通常O(1)ハッシュマップを超えるHAMT階層型ハッシュマップ。
  • Inverted Indexは、さまざまな検索用語に関連付けられているドキュメントを高速に取得するために使用されるため、検索エンジンの分野でよく知られています。

これらのすべてではないにしても、ほとんどがNIST のアルゴリズムとデータ構造の辞書に記載されています。


18

ボールツリー。彼らが人々をくすくす笑わせるからといって。

ボールツリーは、メトリック空間のポイントにインデックスを付けるデータ構造です。 これらを構築する方法についての記事を次に示します。 それらは、ある点への最近傍を見つけるため、またはk平均を加速するためによく使用されます。


これらは一般に「視点」ツリーまたはvpツリーとしても知られています。en.wikipedia.org/wiki/Vp-tree
Edward KMETT 2010

17

実際にはデータ構造ではありません。動的に割り当てられた配列を最適化する方法の詳細ですが、Emacsで使用されているギャップバッファーは一種のクールです。


1
私は間違いなくそれをデータ構造であると考えます。
Christopher Barber

これは、SwingテキストコンポーネントをサポートするDocument(たとえば、PlainDocument)モデルの実装方法とまったく同じです。1.2より前のバージョンでは、ドキュメントモデルは単純な配列であると考えていました。これにより、大きなドキュメントの挿入パフォーマンスがひどくなります。彼らがギャップバッファーに移動するとすぐに、すべてが再び世界に正解となりました。
Riyad Kalla

16

フェンウィックツリー。これは、指定された2つのサブインデックスiとjの間で、ベクトル内のすべての要素の合計をカウントするデータ構造です。簡単な解決策、最初から合計を事前に計算することはアイテムを更新することを許可しません(あなたは追いつくためにO(n)の仕事をしなければなりません)。

Fenwick Treesを使用すると、O(log n)で更新およびクエリを実行できます。また、Fenwick Treesの機能は非常にクールでシンプルです。それはフェンウィックの最初の論文で本当によく説明されています。

http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol24/issue3/spe884.pdf

その父であるRQMツリーも非常に優れています。これにより、ベクトルの2つのインデックス間の最小要素に関する情報を保持でき、O(log n)の更新とクエリでも機能します。私は最初にRQMを教え、次にフェンウィックツリーを教えるのが好きです。


これは重複していると思います。たぶん、あなたは前の答えに追加したいですか?
フランソワG

また、関連するのは、あらゆる種類の範囲クエリを実行するのに役立つセグメントツリーです。
dhruvbird 2011年



12

これはかなりドメイン固有ですが、ハーフエッジデータ構造はかなりきちんとしています。これは、ポリゴンメッシュ(面エッジ)を反復処理する方法を提供します。これは、コンピュータグラフィックスと計算ジオメトリで非常に役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.