c++中set和unordered_set的区别是什么?

what is the difference between set and unordered_set in C++?

本文关键字:set 区别 是什么 unordered c++      更新时间:2023-10-16

我遇到了这个很好的问题,这是类似的,但不完全相同,因为它谈论的是Java,它有不同的哈希表实现,由于有同步的访问/mutator:Java中的HashMap和Hashtable有什么区别?

那么setunordered_set的c++实现有什么不同呢?这个问题当然可以扩展到mapunordered_map,等等其他c++容器。

这是我的初步评估:

set:虽然标准没有明确要求将其实现为树,但时间复杂度约束要求其查找/插入操作,这意味着它将始终实现为树。通常是RB树(如GCC 4.8中所见),它是高度平衡的。因为它们是高度平衡的,所以它们对find()具有可预测的时间复杂度

优点:紧凑(与其他DS相比)

Con: Access time complexity is O(lgn)

unordered_set:虽然标准没有明确要求将其实现为树,但时间复杂度约束要求其查找/插入操作,这意味着它将始终作为哈希表实现。

优点:

  1. 更快(承诺为搜索平摊O(1))
  2. 与tree-DS相比,容易将基本原语转换为线程安全的

缺点:

  1. 查找不保证是O(1)。理论上最坏的情况是0 (n)。
  2. 不像树那样紧凑(为了实际目的,负载因子从来不是1)。

注意:哈希表的0(1)来自于没有碰撞的假设。即使负载因子为0.5,每秒钟的变量插入都会导致碰撞。可以观察到,哈希表的负载因子与访问其中一个元素所需的操作次数成反比。我们越减少#操作,哈希表越稀疏。如果存储的元素的大小与指针相当,则开销相当大。

我是否错过了性能分析中map/set之间应该知道的任何区别?

我想你基本上已经回答了你自己的问题,然而,

不像树那样紧凑。(对于实际目的,负载系数从不为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将以"随机"顺序返回值。

另一个区别(虽然与性能无关)是set插入不会使迭代器无效,而unordered_set插入如果触发重新散列则可以。在实践中,这是一个非常小的问题,因为对实际元素的引用仍然有效。

玉石已经很好地解决了空间效率和其他问题;我将评论这个问题的其他几个部分……

哈希表的0(1)来自于没有碰撞的假设。

那不是真的。O(1)的意思并不是说第一次查找尝试总是会成功,而是平均来说,需要的尝试次数是恒定的,而不是随着值的增加而增加。例如,使用unordered_set或…_map, max_load_factor在构造时默认为1.0,如果负载因子接近这个值,并且有一个好的哈希函数,那么无论表中有多少值,哈希到任何一个bucket的元素的平均数量将在2左右。

即使负载因子为0.5,每秒钟的变量插入都会导致碰撞。

对,但它并不像你直观地想象的那么可怕:在1.0负载系数下,平均链长为2并不坏。

可以观察到哈希表的负载因子是相反的与访问a所需的操作次数成正比元素。我们越少#操作,越稀疏哈希表

这绝对是相关的(不是反向的)。

在某些情况下set更方便。

例如使用vector作为key:

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_setset的更多区别见:https://stackoverflow.com/a/52203931/6329006