web-dev-qa-db-ja.com

ハッシュセットとツリーセット

私はいつも木が好きでした、そのニースO(n*log(n))とそれらのきちんとしたこと。しかし、私が今までに知っているすべてのソフトウェアエンジニアが、なぜTreeSetを使うのかと指摘してきました。 CSの背景からすると、あなたが使うことはそれほど問題にならないと思いますし、(Javaの場合)ハッシュ関数やバケツをいじっても構いません。

どの場合にHashSetよりTreeSetを使うべきですか?

472
heymatthew

HashSetはTreeSetよりはるかに高速です(add、remove、containsなどのほとんどの操作では一定時間対ログ時間)が、TreeSetのような順序保証はありません。

ハッシュセット

  • このクラスは基本操作(add、remove、contains、size)に対して一定の時間パフォーマンスを提供します。
  • 要素の順序が時間の経過とともに一定に保たれることを保証するものではありません
  • 繰り返しパフォーマンスは、HashSetの初期容量負荷率に依存します。
    • デフォルトの負荷率を受け入れるのは非常に安全ですが、セットが大きくなると予想されるサイズの約2倍の初期容量を指定することをお勧めします。

TreeSet

  • 基本操作(追加、削除、含む)のlog(n)時間コストを保証
  • setの要素がソートされることを保証します(昇順、自然、またはコンストラクタを介してユーザーが指定したもの)(implements SortedSet
  • 反復性能のための調整パラメータを提供していません
  • first()last()headSet() 、および tailSet() etcのような順序付きセットを扱うためのいくつかの便利な方法を提供しています。

重要なポイント

  • どちらも重複のない要素のコレクションを保証します
  • 通常、HashSetに要素を追加してからコレクションをTreeSetに変換して、重複のないソート済みのトラバーサルを実行するほうが高速です。
  • これらの実装はどれも同期されていません。つまり、複数のスレッドが同時にセットにアクセスし、少なくとも1つのスレッドがそのセットを変更する場合は、外部で同期化する必要があります。
  • LinkedHashSet はある意味でHashSetTreeSetの中間です。リンクリストを実行するハッシュテーブルとして実装されていますが、 TreeSetで保証されているソート済みトラバーサルと同じではない挿入順反復を提供します

したがって、使い方の選択は完全にあなたのニーズに依存しますが、たとえあなたが順序付きコレクションを必要とするとしても、あなたはまだセットを作成しそれからTreeSetに変換するためにHashSetを好むべきだと思います.

  • 例えばSortedSet<String> s = new TreeSet<String>(hashSet);
838
sactiw

TreeSetについてまだ言及されていない利点の1つは、より大きな「局所性」があることです。これは、(1)2つのエントリが順番に近い場合は、近くに配置されます。データ構造、したがってメモリ内。 (2)この配置では、ローカリティの原則を利用します。つまり、類似のデータには、類似の頻度でアプリケーションからアクセスされることがよくあります。

これはTreeSetとは対照的です。HashSetは、キーが何であっても、エントリをメモリ全体に広げます。

ハードドライブからの読み取りにかかる待ち時間のコストがキャッシュまたはRAMからの読み取りにかかるコストの数千倍であり、データが実際に局所性を持ってアクセスされる場合は、TreeSetがはるかに良い選択になります。

39
Carl Andersen

HashSetは要素にアクセスするためのO(1)なので、確かに重要です。しかし、セット内のオブジェクトの順序を維持することは不可能です。

TreeSetは、(挿入の順序ではなく値の観点から)順序を維持することが重要な場合に役立ちます。しかし、すでに述べたように、要素へのアクセス時間が遅くなるような順序で取引しています。基本操作ではO(log n)です。

javadocからTreeSet へ:

この実装は、基本操作(addremove、およびcontains)のlog(n)時間コストを保証します。

25
duffymo

1.HashSetはnullオブジェクトを許可します。

2.TreeSetはnullオブジェクトを許可しません。 null値を追加しようとすると、NullPointerExceptionがスローされます。

3.HashSetはTreeSetよりはるかに高速です。

例えば.

 TreeSet<String> ts = new TreeSet<String>();
 ts.add(null); // throws NullPointerException

 HashSet<String> hs = new HashSet<String>();
 hs.add(null); // runs fine
21
SuReN

素敵な 視覚的な答え @shevchykによるMapsを基にしています。

╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
║   Property   ║       HashSet       ║      TreeSet      ║     LinkedHashSet   ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║              ║  no guarantee order ║ sorted according  ║                     ║
║   Order      ║ will remain constant║ to the natural    ║    insertion-order  ║
║              ║      over time      ║    ordering       ║                     ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ Add/remove   ║        O(1)         ║     O(log(n))     ║        O(1)         ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║              ║                     ║   NavigableSet    ║                     ║
║  Interfaces  ║         Set         ║       Set         ║         Set         ║
║              ║                     ║    SortedSet      ║                     ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║              ║                     ║    not allowed    ║                     ║
║  Null values ║       allowed       ║ 1st element only  ║      allowed        ║
║              ║                     ║     in Java 7     ║                     ║
╠══════════════╬═════════════════════╩═══════════════════╩═════════════════════╣
║              ║   Fail-fast behavior of an iterator cannot be guaranteed      ║
║   Fail-fast  ║ impossible to make any hard guarantees in the presence of     ║
║   behavior   ║           unsynchronized concurrent modification              ║
╠══════════════╬═══════════════════════════════════════════════════════════════╣
║      Is      ║                                                               ║
║ synchronized ║              implementation is not synchronized               ║
╚══════════════╩═══════════════════════════════════════════════════════════════╝
20
kiedysktos

ほとんどの場合にHashSetが使用されるのは、オペレーションが(平均して)O(log n)ではなくO(1)であるためです。セットに標準的な項目が含まれている場合は、それがあなたのために行われたように「ハッシュ関数をいじっている」ことはありません。セットにカスタムクラスが含まれている場合は(hashCodeを使用するためにHashSetを実装する必要がありますが、有効なJavaにはその方法が示されています)、TreeSetを使用する場合はComparableにするかComparatorを指定する必要があります。クラスに特定の順序がない場合、これは問題になる可能性があります。

非常に小さいセット/マップ(<10項目)にTreeSet(または実際にはTreeMap)を使用したことがありますが、実際に利益があるかどうかを確認していません。大規模なセットの場合、違いはかなりのものになる可能性があります。

ソートが必要な場合はTreeSetが適切ですが、それでも更新が頻繁でソート結果の必要性が低い場合でも、内容をリストまたは配列にコピーしてソートする方が速い場合があります。

13
Kathy Van Stone

頻繁な再ハッシュ(またはHashSetのサイズ変更ができない場合は衝突)を引き起こすのに十分な要素を挿入していない場合、HashSetは確かに一定時間アクセスの利点をもたらします。しかし、伸びや縮みが非常に多いセットでは、実装によってはTreesetsを使用すると実際にパフォーマンスが向上することがあります。

メモリ使用可能な場合、償却時間は機能的な赤黒木でO(1)に近くなります。 Okasakiの本は私がやってのけることができるよりもっと良い説明を持っているでしょう。 (または 彼の出版物リスト を参照)

10
JasonTrue

HashSetの実装は、もちろん、はるかに高速です。順序がないため、オーバーヘッドが少なくなります。 JavaでのさまざまなSet実装の良い分析は http://Java.Sun.com/docs/books/tutorial/collections/implementations/set.html にあります。

そこでの議論はまたTree vs Hash問題への興味深い「ミドルグラウンド」アプローチを指摘しています。 JavaはLinkedHashSetを提供します。これは、「挿入指向」のリンクリストが実行されるHashSetです。つまり、リンクリストの最後の要素も最後にHashに挿入されます。これにより、TreeSetのコスト増を招くことなく、順序付けされていないハッシュの誤解を避けることができます。

7
Joseph Weissman

TreeSet は2つのソートされたコレクションのうちの1つです(もう1つはTreeMapです)。これは赤黒の木構造を使用していますが(ただし、あなたは知っていましたが)、要素は自然順序に従って昇順で並ぶことを保証します。オプションとして、ComparableまたはComparatorを使用して、(要素のクラスで定義された順序に依存するのではなく)順序をどのようにするかについて独自の規則をコレクションに与えることができるコンストラクタを使用してTreeSetを構築できます

そして LinkedHashSet は、すべての要素にわたって二重にリンクされたリストを維持するHashSetの順序付きバージョンです。繰り返しの順序が気になる場合は、HashSetの代わりにこのクラスを使用してください。 HashSetを反復処理するとき、順序は予測不可能ですが、LinkedHashSetを使用すると、要素が挿入された順序で要素を反復処理できます。

4
subhash laghate

あなたはオレンジを持つことができるのになぜリンゴがありますか?

あなたのコレクションが大きく、読み書きが膨大で、CPUサイクルにお金を払っているのであれば、コレクションの選択は、パフォーマンスを向上させる必要がある場合にのみ意味があります。しかし、ほとんどの場合、これは重要ではありません。ここ数ミリ秒の間、人間の目では気づかれないままになります。それほど問題にならないのなら、なぜアセンブラやCでコードを書いていないのでしょうか。 [別の議論を始める]。つまり、あなたが選んだコレクションを何でも使って満足しているのであれば、それが問題になります。ソフトウェアは可鍛性があります。必要に応じてコードを最適化してください。ボブおじさんは時期尚早の最適化がすべての悪の根源だと言います。 アンクルボブはそう言う

3
user924272

技術的な考慮事項、特にパフォーマンスに関して、多くの回答がなされています。私によると、TreeSetHashSetの選択は重要です。

しかし、私はむしろその選択はによって推進されるべきだと言いたい 概念的 まず検討事項。

操作する必要があるオブジェクトに対して、自然な順序付けが意味を成さない場合は、TreeSetを使用しないでください。
SortedSetを実装しているので、ソートセットです。そのため、関数compareToをオーバーライドする必要があります。これは、関数equalsを返すものと一致している必要があります。たとえば、Studentというクラスのオブジェクトのセットがある場合、生徒間には自然な順序付けがないため、TreeSetは意味をなさないと思います。あなたはそれらの平均的な等級でそれらを注文することができます、しかし、これは「自然な順序」ではありません。 2つのオブジェクトが同じ生徒を表す場合だけでなく、2人の異なる生徒が同じ学年を持つ場合も、関数compareToは0を返します。 2番目のケースでは、equalsはfalseを返します(2人の生徒が同じ学年を持っているときに後者をtrueにすると決めない限り、equals関数は誤解を招くような意味になります)。
equalscompareToの間のこの一貫性はオプションですが、強くお勧めします。そうでなければ、インターフェースSetの規約が破られ、あなたのコードが他の人々に誤解を招くようになり、その結果、予期しない動作を引き起こす可能性があります。

この link はこの質問に関する良い情報源かもしれません。

3
Marek Stanley

メッセージ編集( complete rewrite )順序が問題にならないときは、そのときにしてください。どちらもLog(n)を返すはずです - どちらかが他方よりも5%以上速いかどうかを確認するのに役立ちます。 HashSetはループ内でO(1)テストを行うことができるかどうかを明らかにすることができます。

1
Nicholas Jordan