2013-04-18 15 views
44

は、アクセサ/ミューテータ Differences between HashMap and Hashtable?C++でsetとunordered_setの違いは何ですか?

を同期化したのおかげで、ハッシュテーブルの異なる実装を持つJava、話して以来、そう違いは何で似ているこの良い質問、出くわしたけど全然同じC++ではsetとunordered_setの実装ですか? この質問は、他のC++コンテナのvs unordered_mapなどへのマッピングに拡張できます。ここで

がを設定

私の最初の評価である:標準のdoesntが明示的にツリーとして実装することを要求しますが、検索/挿入のためにその業務を尋ね時間複雑度の制約は、それはいつものように実装されることを意味します木。 通常、(GCC 4.8に見られるような)RBツリーとして、高さのバランスがとられています。 それらが高バランスであるため、それらは(検索のための予測可能な時間複雑度を有する)

長所:コンパクト(比較して、他のDSと比較して)

コン:アクセス時間複雑度はO(LG n)が

あります

unordered_set:標準ではツリーとして実装するよう明示的に要求していますが、検索/挿入の操作を要求する時間複雑度の制約は、常にハッシュテーブルとして実装されることを意味します。

長所:ツリー-DS

短所と比較して、スレッドセーフするための基本的なプリミティブを変換する(約束は検索のためのO(1)償却)

  • 簡単

    1. の高速化:

      1. (1)Therotical最悪の場合はO(n)の
      2. であるOであることを保証しませ見上げて0
      3. 木ほどコンパクトではありません。 (実用的な負荷係数は決して1ではありません)

      注:ハッシュテーブルのO(1)は、衝突がないことを前提としています。負荷係数が0.5であっても、すべての2番目の可変挿入が衝突につながります。 ハッシュテーブルの負荷係数は、ハッシュテーブル内の要素にアクセスするために必要な操作の数に反比例することが分かります。より多くは、#オペレーションを減らし、より疎なハッシュテーブルを作成します。記憶された要素がポインタに匹敵するサイズである場合、オーバヘッドはかなり重要である。

      編集:ほとんどの質問には十分な答えが含まれているので、私は に質問を変更しています。「マップ/設定の違いが分からないのですか?

  • +1

    'std :: set 'の要素は特定の順序でトラバース可能でなければなりません。これは、挿入、検索、および削除操作が 'O(lg n)'である実際の理由です。 – pyon

    +0

    @EduardoLeón:O(lg n)はDSのようなツリーの副作用だと思います。それはまた、アイテムが横断されたときに特定の順序を有することを説明する。私は確信が持てませんが、C++の 'set'の要件は '特定の順序'であることがわかりません。私は間違っている可能性がある。 –

    +4

    "* LookupはO(1)であることは保証されません。Rotatory worst caseはO(n)*です。"それは "あなたがハッシュ関数を書く方法がわからない"ので、 "con" –

    答えて

    23

    私はあなたが、一般的に、あなた自身の質問に答えたと思う、しかし、この:

    ないよう木のようにコンパクト。 (実用的な目的のために負荷率は決して1ではない)

    は必ずしも真ではない。タイプTのツリーの各ノード(赤黒のツリーであると仮定します)は、少なくとも2 * pointer_size + sizeof(T) + sizeof(bool)に等しいスペースを使用します。これは、ツリーに各ツリーノードのparentポインタが含まれているかどうかによって、3 * pointer sizeになる場合があります。

    これをハッシュマップと比較してください:あなたが言ったように、load factor < 1という事実のために、各ハッシュマップに無駄な配列スペースがあります。しかし、ハッシュマップが連鎖のために単独でリンクされたリストを使用すると仮定すると、挿入される各要素はsizeof(T) + pointer sizeになります。

    この分析では、配置によって使用される余分なスペースに起因するオーバーヘッドは無視されることに注意してください。

    小さいサイズ(したがって、任意の基本タイプ)を持つ任意の要素Tの場合、ポインタのサイズおよびその他のオーバーヘッドが支配的です。 > 0.5の負荷係数(たとえば)の場合、std::unordered_setは実際には相当するメモリより少ないメモリを使用する可能性があります(std::set)。

    もう1つの大きな欠点は、std::setを反復すると、指定された比較関数に基づいて最小値から最大値への順序が保証され、std::unordered_setを反復すると値が「ランダム」の順序で返されるという事実です。

    +0

    @PeteBecker '' O(1) 'ルックアップのために、それは効果的にリストの配列(または単に鳩のハッシュが使われるならば配列)に強制されます - ' O( 1) 'がこれを強制する。それぞれのバケットが利用しているものを参照している場合は、私の投稿をもう一度読んで、明示的に私は単一リンクリストを仮定しています(これは確かに標準によって強制されていません)。 – Yuushi

    +0

    私はそのメッセージを削除したと思った。私は今それを削除しました。 –

    9

    もう1つの違いは(パフォーマンス関連ではありませんが)、set挿入はイテレータを無効にしませんが、unordered_set挿入は再ハッシュをトリガする場合に挿入できます。実際のところ、実際の要素への参照は有効なので、これはかなり軽度の問題です。

    +0

    これは、 'set'がrb-treeとして実装されていると、挿入がツリーの再調整を引き起こす可能性があるからです。 –

    +0

    イテレータは、内部ツリーノードへのポインタの観点から(AFAIK、常に)実装できるためです。リバランス操作でノードを作成または破棄する必要はなく、左/右/親ポインタをシャッフルするだけで済みます。そのあとで、以前に有効なイテレータは有効なノードを指し示したままであり、ツリーを走査するのに必要なすべてのものを得ることができます。 – dhaffey

    1

    ゆうしは、空間効率などの点について十分に取り組んでいます。私がコメントする質問のちょっと他の部分...

    ハッシュテーブルのO(1)は、衝突がないことを前提としています。

    これは正しくありません。 O(1)が意味することは、最初のルックアップの試みが常に成功するということではなく、値の数が増えるにつれて増加するものではなく、平均して一定の試行回数が必要であるということです。たとえば、unordered_setまたは... _mapの場合、max_load_factorはデフォルトで1.0になり、負荷係数が良好なハッシュ関数を使用して負荷係数に近づくと、の平均値のいずれかのバケットにハッシュする要素数は関係なく約2になります表にある値の数

    0.5の負荷係数でも、2回目の可変挿入がすべて衝突につながります。

    真実ですが、直感的に思えるほど致命的ではありません。1.0の負荷係数での平均チェーン長は2であり、悪くありません。

    ハッシュテーブルの負荷係数が逆それに 要素にアクセスするために必要な操作の数に比例 であることが観察されました。より多くは、#オペレーションを減らし、より疎なハッシュテーブルを作成します。

    確かに相関があります(逆ではありません)。

    +0

    l.f.を達成できますか? .5衝突なし。少なくとも2回目の挿入ではありませんか? – Yola

    +0

    @Yola:新しいアイテムの配置が事実上ランダムであるが再現性があるので、入力に関係なくうまくいかない一般目的のハッシュ関数を使用する:ハーフバケットはすでに使用中であるため、衝突。実際には、多くの言語/ライブラリには、単純に整数をそのまま渡すハッシュ実装が付属しているため、鍵が増える傾向がある場合、連続したバケットにうまくマッピングされ、頻繁に発生する衝突(およびキャッシュの面倒さ)この汎用目的のランダム反復可能なハッシュを使用します。 –

    +0

    極端な場合、['gperf'](https://www.gnu.org/software/gperf/)のようなプログラムは、一定のキーのセットに対して完璧なハッシュ(つまり0の衝突)を行うためのソースコードを生成することができます。それは実行時まで未知の入力に対しては役に立たない。 –

    関連する問題