遇到了这个很好的问题,这个问题很相似,但由于具有同步的访问器/ mutators,因此它讨论了Java,它具有不同的哈希表实现 Differences between HashMap and Hashtable?
那么set和unordered_set的C ++实现有什么不同呢? 对于其他C ++容器,这个问题可以扩展到map vs unordered_map等等。
这是我的初步评估
set :虽然标准并未明确要求将其实现为树,但时间复杂度约束要求查找/插入操作,这意味着它将始终以树形式实现。 通常作为RB树(如GCC 4.8中所见),它是高度平衡的。 由于它们是高度平衡的,因此它们具有可预测的find()
时间复杂度优点:紧凑型(与其他DS相比)
Con:访问时间复杂度为O(lg n)
unordered_set :虽然标准并未明确要求将其实现为树,但时间复杂度约束要求查找/插入操作,这意味着它将始终实现为哈希表。 / p>
优点:
缺点:
注意: 哈希表的O(1)来自假设没有冲突。即使负载系数为.5,每隔一次变量插入也会导致碰撞。 可以观察到,散列表的负载因子与访问其中的元素所需的操作数成反比。我们减少了#operations,sparser hash-table。当存储的元素大小与指针相当时,开销就非常大。
编辑:由于大多数人都说问题包含足够的答案,我正在将问题改为 “我是否错过了应该知道的性能分析的地图/集之间的任何区别?”
答案 0 :(得分:26)
我认为您通常会回答您自己的问题,但是:
不像树一样紧凑。 (出于实际目的,载荷因子永远不会是1)
不一定是真的。对于类型T
,树的每个节点(我们假设它是红黑树)利用至少等于2 * pointer_size + sizeof(T) + sizeof(bool)
的空间。这可能是3 * pointer size
,具体取决于树是否包含每个树节点的parent
指针。
将此与哈希映射进行比较:由于您load factor < 1
所说的事实,每个哈希映射都会浪费数组空间。但是,假设哈希映射使用单链表进行链接(实际上,没有真正的理由),插入的每个元素仅采用sizeof(T) + pointer size
。
请注意,此分析忽略了可能来自对齐使用的额外空间的任何开销。
对于任何具有小尺寸的元素T
(因此,任何基本类型),指针的大小和其他开销占主导地位。在> 0.5
的加载因子(例如)下,std::unordered_set
可能确实比同等的std::set
使用更少的内存。
另一个重要的缺点是,基于给定的比较函数,迭代std::set
保证产生从最小到最大的排序,而迭代std::unordered_set
将返回&#34;随机&#34;中的值订购。
答案 1 :(得分:11)
另一个区别(虽然与性能无关)是set
插入不会使迭代器无效,而unordered_set
插入可以触发重新散列。在实践中,它是一个非常小的问题,因为对实际元素的引用仍然有效。
答案 2 :(得分:2)
哈希表的O(1)来自假设没有碰撞。
那不是真的。 O(1)的含义并不是第一次查找尝试总是会成功,而是 - 平均而言 - 需要一定数量的尝试,而不是随着值的数量增长而增长。例如,使用unordered_set
或... _map
时,max_load_factor
在构造时默认为1.0,如果加载因子接近具有良好哈希函数的值,则平均值< / em>无论表中有多少值,散列到任何一个存储桶的元素数量都将为2左右。
即使负载系数为.5,每隔一次变量插入也会导致碰撞。
是的,但它并没有像你直觉所期望的那样可怕:在1.0载荷因子下平均链长为2也不错。
可以观察到哈希表的负载因子是反向的 与访问a所需的操作数成比例 元素在里面。我们减少了#operations,sparser hash-table。
肯定有相关性(它不是反向的)。
答案 3 :(得分:0)
在某些情况下,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
vector<int>
会覆盖set
的原因,vector
之所以位于operator<
中的原因。
但是,如果使用unordered_set<vector<int>>
,则必须为vector<int>
创建一个哈希函数,因为vector没有哈希函数,因此必须定义一个像这样的变量:
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