web-dev-qa-db-ja.com

バイナリツリーとリンクリストとハッシュテーブル

私が取り組んでいるプロジェクトのシンボルテーブルを構築しています。シンボルテーブルの保存と作成に使用できるさまざまな方法の長所と短所について、人々の意見はどうなっているのだろうと思いました。

かなりの量の検索を実行しましたが、最も一般的に推奨されるのはバイナリツリーまたはリンクリストまたはハッシュテーブルです。上記のすべての長所と短所は何ですか? (C++での作業)

72
benofsky

ユースケースは、「データを1回挿入(たとえば、アプリケーションの起動)してから、多くの読み取りを実行しますが、余分な挿入はほとんどありません」と考えられます。

したがって、必要な情報を検索するために高速なアルゴリズムを使用する必要があります。

したがって、HashTableは、キーオブジェクトのハッシュを生成し、それを使用してターゲットデータにアクセスするだけで、O(1)であるため、使用するのに最適なアルゴリズムだと思います。その他はO(N)(サイズNのリンクリスト-リストを一度に1つずつ、平均N/2回繰り返す必要があります)およびO(log N)(バイナリツリー-反復ごとにサーチスペースを半分にします-ツリーのバランスがとれている場合のみです。したがって、これは実装に依存します。

HashTableにデータ用に十分なスペース(バケット)があることを確認してください(R.e.、この投稿に関するSorazのコメント)。ほとんどのフレームワーク実装(Java、.NETなど)は、実装について心配する必要のない品質です。

大学でデータ構造とアルゴリズムのコースを受講しましたか?

48
JeeBee

これらのデータ構造間の標準的なトレードオフが適用されます。

  • 二分木
    • 実装する中程度の複雑さ(ライブラリから取得できないと仮定)
    • 挿入はO(logN)です
    • ルックアップはO(logN)です
  • リンクリスト(未ソート)
    • 実装する複雑さが低い
    • 挿入はO(1)です
    • ルックアップはO(N)です
  • ハッシュテーブル
    • 実装が非常に複雑
    • 挿入は、平均O(1)
    • ルックアップはO(1)平均で
74
Darron

誰もが忘れているように見えるのは、小さなNの場合、IEテーブル内のいくつかのシンボルでは、リンクリストはハッシュテーブルよりもはるかに高速になりますが、理論的には漸近的な複雑さが実際に高くなります。

Cのプログラミングに関するPikeのノートからの有名なqouteがあります:「ルール3。nが小さいとファンシーアルゴリズムは遅く、nは通常小さいです。ファンシーアルゴリズムには大きな定数があります。nが頻繁に大きくなることを知るまで、派手にならないでください。」 http://www.lysator.liu.se/c/pikestyle.html

小さなNを扱うかどうかはあなたの投稿からわかりませんが、大きなNに最適なアルゴリズムが小さなNに必ずしも適しているわけではないことを常に覚えておいてください。

42

次のことがすべて当てはまるようです。

  • キーは文字列です。
  • 挿入は1回行われます。
  • ルックアップは頻繁に行われます。
  • キーと値のペアの数は比較的少ない(たとえば、Kほど少ない)。

その場合、これらの他の構造のいずれかでソートされたリストを検討できます。ソートされたリストは挿入時にO(N)であるのに対して、リンクされたリストではO(1)ハッシュテーブル、およびO(log2N)平衡二分木の場合。しかし、ソートされたリストでのルックアップは、これらの他のどの構造よりも高速である可能性があります(これについては後ほど説明します)。また、一度にすべての挿入を実行する場合(または、すべての挿入が完了するまでルックアップを必要としない場合)、挿入をO(1)に単純化して、はるかに速いソートを行うことができます)さらに、ソートされたリストは他のどの構造よりも少ないメモリを使用しますが、これが問題になる可能性がある唯一の方法は、小さなリストが多数ある場合です。テーブルは、ソートされたリストを上回る可能性があります。

ソートされたリストで検索が高速になるのはなぜですか?まあ、それはリンクリストよりも速いことは明らかです、後者のO(N)ルックアップ時間。バイナリツリーでは、ルックアップはO(log2 N)ツリーのバランスが完全に保たれている場合。ツリーのバランスを保つと(たとえば、赤黒)、複雑さと挿入時間が長くなります。さらに、リンクリストとバイナリツリーの両方で、各要素は個別に割り当てられます1 node。これは、ポインターを逆参照する必要があり、潜在的に大幅に変化するメモリアドレスにジャンプする必要があるため、キャッシュミスの可能性が高くなることを意味します。

ハッシュテーブルについては、おそらくStackOverflowで acouple of その他の質問 を読む必要がありますが、ここでの主な関心事項は次のとおりです。

  • ハッシュテーブルは、最悪の場合O(N)に縮退する可能性があります。
  • ハッシュのコストはゼロ以外であり、一部の実装では、特に文字列の場合、それが重要になる可能性があります。
  • リンクリストとバイナリツリーのように、各エントリはnodeであり、キーと値だけでなく、一部の実装では個別に割り当てられたもの以上を格納するため、メモリを増やし、キャッシュミスの可能性を増やします。

もちろん、これらのデータ構造の実行方法を本当に気にする場合は、テストする必要があります。ほとんどの一般的な言語では、これらの適切な実装を見つけるのにほとんど問題はないはずです。これらのデータ構造のそれぞれで実際のデータの一部をスローし、どれが最もパフォーマンスが良いかを確認するのはそれほど難しくないはずです。

  1. 実装でノードの配列を事前に割り当てることができます。これは、キャッシュミスの問題に役立ちます。リンクリストまたはバイナリツリーの実際の実装では、これを見たことはありません(もちろん、すべてを見たわけではありません)。ただし、nodeオブジェクトは必ずキー/値のペアよりも大きくなるため、キャッシュミスの可能性はわずかに高くなります。
8
P Daddy

私はビルの答えが好きですが、実際には物事を合成しません。

3つの選択肢から:

リンクリストは、(O(n))からのアイテムの検索が比較的遅くなります。したがって、テーブルにlotのアイテムがある場合、または多くのルックアップを行う場合、それらは最良の選択ではありません。しかし、それらは簡単に作成でき、作成も簡単です。テーブルが小さい場合、および/またはテーブルの作成後に1回だけ小さなスキャンを実行する場合は、これが選択される可能性があります。

ハッシュテーブルは非常に高速です。ただし、それが機能するためには、入力に適したハッシュを選択する必要があります。また、多くのハッシュの衝突なしにすべてを保持するのに十分な大きさのテーブルを選択する必要があります。つまり、入力のサイズと量について何かを知る必要があります。これを台無しにすると、本当に高価で複雑なリンクリストのセットになってしまいます。テーブルがどれくらい大きくなるかを事前に知っていない限り、ハッシュテーブルを使用しないでください。これは、「受け入れられた」答えとは異なります。ごめんなさい。

それは木を残します。ただし、ここには選択肢があります。バランスを取るか、バランスしないか。ここにあるCおよびFortranコードでこの問題を研究して私が見つけたのは、シンボルテーブルの入力が十分にランダムになる傾向があり、ツリーのバランスをとらないことでツリーレベルまたは2つだけが失われることです。バランスの取れたツリーは、要素の挿入が遅く、実装が難しいことを考えると、気にしません。ただし、既にデバッグされたコンポーネントライブラリ(例:C++のSTL)にアクセスできる場合は、バランスツリーを使用することもできます。

7
T.E.D.

気をつけるべきことがいくつかあります。

  • バイナリツリーはO(log n)ルックアップのみを持ち、ツリーがbalancedの場合に複雑度を挿入します。シンボルがかなりランダムに挿入される場合、これは問題になりません。それらが順番に挿入されると、リンクリストが作成されます。 (特定のアプリケーションでは、どのような順序でもないはずですので、大丈夫です。)シンボルが整然となる可能性がある場合は、 Red-Black Treeの方が適していますオプション。

  • ハッシュテーブルはO(1)平均挿入とルックアップの複雑さを与えますが、ここにも注意事項があります。ハッシュ関数が悪い場合(および本当に悪い)あなたもここでリンクされたリストを構築することができます。しかし、どんな合理的な文字列ハッシュ関数でも行うべきですので、この警告は本当にそれが起こる可能性があることを確認するためだけです予想される入力範囲でハッシュ関数が多くの衝突を起こさないことをテストすることができるはずで、大丈夫です。もう1つの小さな欠点は、固定サイズのハッシュテーブルを使用している場合です。ハッシュテーブルの実装は、特定のサイズに達すると成長します(より正確な負荷係数、詳細については here を参照してください)。これは、10個のバケットに100万個のシンボルを挿入するときに発生する問題を回避するためです。これにより、平均サイズが100,000のリンクリストが10個になります。

  • 本当に短いシンボルテーブルがある場合にのみ、リンクリストを使用します。実装は最も簡単ですが、リンクリストの場合の最高のパフォーマンスは、他の2つのオプションの最悪の場合のパフォーマンスです。

6
Bill the Lizard

他のコメントは要素の追加/取得に焦点を当てていますが、この議論はコレクション全体を反復するために必要なことを考慮せずには完了しません。ここでの簡単な答えは、ハッシュテーブルの反復処理に必要なメモリは少ないが、ツリーに必要な時間は少ないということです。

ハッシュテーブルの場合、(キー、値)のペアを反復処理するメモリオーバーヘッドは、テーブルの容量やテーブルに格納されている要素の数に依存しません。実際、反復には1つまたは2つのインデックス変数のみが必要です。

ツリーの場合、必要なメモリの量は常にツリーのサイズに依存します。反復中に未訪問ノードのキューを維持するか、反復を容易にするために追加のポインターをツリーに追加できます(反復のためにツリーを作成し、リンクリストのように動作させます)が、いずれにしても、反復に追加のメモリを割り当てる必要があります。

しかし、タイミングに関しては状況は逆転します。ハッシュテーブルの場合、反復にかかる時間は、格納されている要素の数ではなく、テーブルの容量に依存します。したがって、容量の10%でロードされたテーブルは、同じ要素を持つリンクリストよりも、反復処理に約10倍時間がかかります。

1
anonymous

もちろん、これはいくつかのことに依存します。リンクリストには、シンボルテーブルとして機能する適切なプロパティがほとんどないため、まさに正しいと言えます。バイナリツリーが既に機能していて、作成とデバッグに時間を費やす必要がない場合は、動作する場合があります。私の選択はハッシュテーブルです。これは多かれ少なかれこの目的のためのデフォルトだと思います。

0
unwind

この質問 はC#のさまざまなコンテナを通過しますが、使用するすべての言語で類似しています。

0

シンボルテーブルが小さいと思わない限り、リンクリストを避けてください。 1000個のアイテムのリストは、その中のアイテムを見つけるために平均500回の反復を必要とします。

バイナリツリーは、バランスが取れている限り、はるかに高速になります。コンテンツを永続化している場合、シリアライズされたフォームはおそらくソートされ、再ロードされると結果のツリーは結果として完全に不均衡になり、リンクされたリストと同じように動作します-それは基本的に何になったか。バランスツリーアルゴリズムはこの問題を解決しますが、シバン全体をより複雑にします。

ハッシュマップは(適切なハッシュアルゴリズムを選択する限り)最適なソリューションのように見えます。環境については言及していませんが、ほぼすべての現代言語にはハッシュマップが組み込まれています。

0
Martin Cowie