【问题标题】:How does C++ STL unordered_map resolve collisions?C++ STL unordered_map 如何解决冲突?
【发布时间】:2014-02-26 10:16:38
【问题描述】:

C++ STL unordered_map 如何解决冲突?

看着http://www.cplusplus.com/reference/unordered_map/unordered_map/,它说“唯一键 容器中的两个元素不能有等价的键。”

这应该意味着容器确实在解决冲突。但是,该页面并没有告诉我它是如何做到的。我知道一些解决冲突的方法,比如使用链表和/或探测。我想知道的是c++ STL unordered_map是如何解决的。

【问题讨论】:

  • 它依赖于实现。语言参考不会告诉您它是如何完成的,因为它没有在标准中指定应该如何完成。
  • libstdc++ 使用线性链接,但 STL 的其他实现可能使用其他技术
  • 从技术上讲,我们根本没有使用 STL。我们使用的是 C++ 标准库,它是一个接口规范。是的,它有多种实现方式。
  • 不,STL 是几十年前的一个库,它的想法被窃取并合并到 C++ 标准库中。每个 C++ 编译器都必须通过它选择的任何方式来实现标准库。人们倾向于将标准库中受 STL 启发的部分称为 STL,尽管这并不正确。
  • “容器中没有两个元素可以有等效的键”是unordered_mapPred 参数的条件。冲突的概念适用于Hash 参数。两个键可能不等价,但仍然可以散列到相同的值——散列冲突的定义。

标签: c++ stl unordered-map


【解决方案1】:

标准对此的定义比大多数人似乎意识到的要多一些。

具体而言,标准要求 (§23.2.5/9):

无序关联容器的元素被组织成桶。具有相同哈希码的键出现在同一个桶中。

该界面包含一个在恒定时间内运行的bucket_count。 (表 103)。它还包括一个bucket_size,它必须在时间上与桶的大小成线性关系。

这基本上是在描述使用碰撞链接的实现。当您确实使用碰撞链时,满足所有要求介于简单和微不足道之间。 bucket_count() 是数组中的元素数。 bucket_size() 是碰撞链中元素的数量。分别在恒定时间和线性时间中获取它们既简单又直接。

相比之下,如果您使用线性探测或双重哈希之类的方法,这些要求几乎无法满足。具体来说,所有哈希到特定值的项目都需要放在同一个桶中,并且您需要能够在恒定时间内对这些桶进行计数。

但是,如果您使用线性探测或双重散列等方法,找到所有散列到相同值的项目意味着您需要对值进行散列,然后遍历表中非空项目的“链”以找出其中有多少散列到相同的值。不过,这与散列到相同值的项目数量不是线性的 - 它与散列到相同 冲突值的项目数量成线性关系。

有了足够的额外工作和相当多的将某些要求的含义延伸到几乎到断点的程度,可能几乎不可能使用碰撞链以外的其他方法创建哈希表,并且至少仍然可以满足要求——但我不确定这是否可能,而且肯定会涉及很多额外的工作。

总结:std::unordered_set(或unordered_map)的所有实际实现无疑都使用碰撞链。虽然使用线性探测或双重哈希可能(几乎)有可能满足要求,但这样的实现似乎损失了很多,几乎没有得到任何回报。

【讨论】:

  • "仍然可以满足要求[使用线性探测或双散列]" / "然后你遍历被占用项目的“链”,找到可以插入该项目的插槽。"与“具有相同哈希码的密钥出现在同一个桶中”不兼容。要求。当然,如果冲突链无论如何都将任何东西放在“同一个桶”中,这是值得商榷的,但是您显然建议将具有相同散列的密钥放在不同的桶中。 23.2.5/9 似乎是一个不幸的限制......探测不仅对“低负载因子”有用 - 避免堆的许多好处......
  • @TonyD:不是真的——在这种情况下,“桶”不再是物理的东西,而只是散列到相同值的所有槽。您必须在 local_iterator 中构建足够的智能来跳过任何碰撞项目,但在我看来它仍然完全有可能。
  • 如果这么灵活,“具有相同哈希码的键出现在同一个桶中。”完全没有意义。同意不同意等。干杯。
  • @TonyD:不是真的。存储桶是一组具有散列到特定值的键的项,并且可以使用 local_iterator 进行迭代。鉴于他们特别避免指定实现,它真的不应该对实现有太大意义。
  • 好吧,从例如找到一个相互同意的“bucket”权威定义。 Dijkstra 对 Google 来说并不容易,所以让我们考虑一下您假设的实现是否可以满足其他要求。根据您需要count 的理由,b.find(k) 不能是最坏的情况 O(b.size()) 其中b 是一个“逻辑”存储桶size()。此外,b.begin(n)b.end(n) 等都需要具有恒定的复杂性,因此需要与您的 count 一起存储在最初的哈希存储桶中。
【解决方案2】:

我发现这个答案是为了寻找如何检测我的类型何时发生冲突,所以我会发布这个以防问题的意图。:

我认为对于“唯一键没有两个容器中的元素可以有等效键”存在一些误解。

看看下面的代码

//pseudocode
std::unordered_map<int, char> hashmap;
hashmap[5] = 'a';
hashmap[5] = 'b'; //replace 'a' with 'b', there is no collision being handled.

我认为 Jerry 的答案是指内部系统,它用于将键缩小到适当的数组索引。

如果您希望为您的类型(使用存储桶)处理冲突,您需要 std::unordered_multimap 并且必须迭代

希望这段代码可以在没有我生成它的上下文的情况下阅读。 它基本上检查存储桶中与哈希关联的任何元素是否是我正在寻找的元素。

//sp is std::shared_ptr
//memo is std::unordered_multimap< int, sp<AStarNode> >

//there's probably multiple issues with this code in terms of good design (like using int keys rather than unsigned)

bool AStar_Incremental::hasNodeBeenVisited(sp<AStarNode> node)
{
    using UMIter = std::unordered_multimap<int, sp<AStarNode> >::iterator;

    bool bAlreadyVisited = false;

    //get all values for key in O(1*)
    int hash = WorldGrid::hashGrid(node->location);
    std::pair<UMIter, UMIter> start_end = memo.equal_range(hash); //bucket range
    UMIter start = start_end.first;
    UMIter end = start_end.second;

    //hopefully this is implemented to be O(m) where m is the bucket size.
    for(UMIter bucketIter = start; bucketIter != end; ++bucketIter)
    {
        sp<AStarNode> previousNode = bucketIter->second;
        sf::Vector2i& previousVisit = previousNode->location;
        if (previousVisit == node->location)
        {
            bAlreadyVisited = true;
            break;
        }
    }

    return bAlreadyVisited;
}

【讨论】:

    猜你喜欢
    • 2012-01-09
    • 2016-02-22
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-27
    • 2016-02-29
    相关资源
    最近更新 更多