この良い質問に出くわしましたが、アクセサ/ミューテータが同期されているため、ハッシュテーブルの実装が異なるJavaについて説明しているため、似ていますがまったく同じではありません HashMapとHashtableの違い?
それでは、setとunordered_setのC++実装の違いは何ですか?この質問はもちろん、他のC++コンテナのmap vs unordered_mapなどに拡張できます。
これが私の最初の評価です
set:標準は明示的にツリーとして実装することを要求していませんが、時間複雑性の制約は検索/挿入の操作を要求しているため、常にツリーとして実装されます。通常、RBツリーとして(GCC 4.8で見られるように)、高さのバランスが取れています。それらは高さのバランスが取れているため、find()の予測可能な時間の複雑さを持ちます。
長所:コンパクト(他のDSと比較して)
Con:アクセス時間の複雑さはO(lg n)です
unordered_set:標準は明示的にツリーとして実装することを要求していませんが、時間複雑性制約は検索/挿入の操作を要求しました。ハッシュテーブルとして実装されます。
長所:
短所:
注:ハッシュテーブルのO(1)は、衝突がないという仮定に基づいています。負荷係数が.5であっても、変数の挿入ごとに衝突が発生します。ハッシュテーブルの負荷係数は、その要素にアクセスするために必要な操作の数に反比例することが観察できます。さらに、#operationsを削減し、ハッシュテーブルを疎にします。格納されている要素のサイズがポインタに匹敵する場合、オーバーヘッドは非常に大きくなります。
編集:ほとんどの質問には十分な答えが含まれていると言われているので、質問を「知っておくべきパフォーマンス分析のマップ/セットの違いを見逃しましたか?」に変更しています。
あなたは一般的にあなた自身の質問に答えたと思いますが、これは:
ツリーほどコンパクトではありません。 (実際の目的では、負荷係数は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
_を反復処理すると、 "順不同。
もう1つの違いは(パフォーマンスに関係しませんが)set
挿入は反復子を無効にしませんが、unordered_set
再ハッシュをトリガーする場合、挿入できます。実際には、実際の要素への参照は引き続き有効であるため、これはかなり小さな懸念事項です。
Yuushiは空間効率やその他のポイントに既に十分に取り組んでいます。私がコメントする質問の他のいくつかの部分...
ハッシュテーブルのO(1)は、衝突がないという仮定に基づいています。
それは真実ではない。 O(1)が意味するのは、最初のルックアップ試行が常に成功するということではなく、平均して、必要な試行の数ではなく、値が大きくなります。たとえば、unordered_set
または..._map
、 max_load_factor
構築時のデフォルトは1.0であり、負荷係数が適切なハッシュ関数でそれに近づくと、average任意の要素にハッシュする要素の数テーブル内の値の数に関係なく、1つのバケットは約2になります。
負荷係数が.5であっても、変数の挿入ごとに衝突が発生します。
確かに、それは直感的に予想されるほど悲惨ではありません。1.0の負荷係数で平均チェーン長が2であることは悪くありません。
ハッシュテーブルの負荷係数は、その要素にアクセスするために必要な操作の数に反比例することが観察できます。さらに、#operationsを削減し、ハッシュテーブルを疎にします。
必ず相関関係があります(逆ではありません)。
場合によっては、set
の方が便利です。
たとえば、vector
をキーとして使用する場合:
set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});
for(const auto& vec:s)
cout<<vec<<endl; // I have override << for vector
// 1 2
// 1 3
set
がvector<int>
をオーバーライドするため、operator<
をvector
に含めることができる理由。
ただし、unordered_set<vector<int>>
を使用する場合、vector<int>
のハッシュ関数を作成する必要があります。ベクターにはハッシュ関数がないため、次のように定義する必要があります。
struct VectorHash {
size_t operator()(const std::vector<int>& v) const {
std::hash<int> hasher;
size_t seed = 0;
for (int i : v) {
seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
return seed;
}
};
vector<vector<int>> two(){
//unordered_set<vector<int>> s; // error vector<int> doesn't have hash function
unordered_set<vector<int>, VectorHash> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});
for(const auto& vec:s)
cout<<vec<<endl;
// 1 2
// 1 3
}
場合によってはunordered_set
がより複雑であることがわかります。
主に引用元: https://stackoverflow.com/a/29855973/6329006
unordered_set
とset
のその他の違いは、これを参照してください: https://stackoverflow.com/a/52203931/6329006