【问题标题】:Is the unordered_map really unordered?unordered_map 真的是无序的吗?
【发布时间】:2011-03-11 17:39:35
【问题描述】:

我对“unordered_map”这个名字感到很困惑。顾名思义,键根本没有排序。但我一直认为它们是按哈希值排序的。还是说错了(因为名字暗示它们不是有序的)?

或者换个说法:这是这个

typedef map<K, V, HashComp<K> > HashMap;

template<typename T>
struct HashComp {
    bool operator<(const T& v1, const T& v2) const {
        return hash<T>()(v1) < hash<T>()(v2);
    }
};

typedef unordered_map<K, V> HashMap;

? (好吧,不完全是,STL 会在这里抱怨,因为可能有键 k1,k2 并且既不是 k1 multimap 并覆盖相等检查。)

或者再次不同:当我遍历它们时,我可以假设键列表是按它们的哈希值排序的吗?

【问题讨论】:

标签: c++ hashmap unordered-map


【解决方案1】:

在回答您编辑的问题时,这两个 sn-ps 根本不等效。 std::map 将节点存储在树结构中,unordered_map 将它们存储在哈希表中*。

键不按其“哈希值”的顺序存储,因为它们根本不按任何顺序存储。相反,它们存储在“桶”中,其中每个桶对应于一系列哈希值。基本上,实现是这样的:

function add_value(object key, object value) {
   int hash = key.getHash();

   int bucket_index = hash % NUM_BUCKETS;
   if (buckets[bucket_index] == null) {
       buckets[bucket_index] = new linked_list();
   }
   buckets[bucket_index].add(new key_value(key, value));
}

function get_value(object key) {
   int hash = key.getHash();

   int bucket_index = hash % NUM_BUCKETS;
   if (buckets[bucket_index] == null) {
       return null;
   }

   foreach(key_value kv in buckets[bucket_index]) {
       if (kv.key == key) {
           return kv.value;
       }
   }
}

显然这是一个严重的简化,实际实现会更先进(例如,支持调整 buckets 数组的大小,可能使用树结构而不是链表作为存储桶,等等),但这应该给出关于如何无法以任何特定顺序取回值的想法。请参阅wikipedia 了解更多信息。


* 从技术上讲,std::mapunordered_map 的内部实现是实现定义的,但该标准要求暗示这些内部实现的操作具有一定的 Big-O 复杂性

【讨论】:

  • 非常感谢。这真的很清楚。我一直认为哈希表将使用树结构在内部实现(就像从哈希值到存储桶的映射)。看来我在那里大错特错了。
  • 这至少被某人再次否决了。这里的所有这些反对意见是什么?投反对票的人可以给点cmets吗?
  • @Albert:我不知道为什么,但不仅仅是链接到 Wikipedia,我添加了一些伪代码用于哈希表的简单实现,它应该显示如何不可能获得任何有意义的“命令”。我知道你已经掌握了要点,但以防万一其他人出现:-)
  • 感谢伪代码。我开始明白了。这让我现在想到了一个新问题:stackoverflow.com/questions/3176761/…
  • 精确度:map 的大 O 复杂度不会强加二叉树(当然也不是红黑树)。有许多结构可以适合这里。其中包括 AVL 树、展开树和跳过列表(顾名思义,最后一个不是树)。
【解决方案2】:

“无序”并不意味着在实现中某处没有线性序列。它的意思是“你不能假设这些元素的顺序”。

例如,人们经常假设条目会按照它们被放入的顺序从哈希映射中出来。但事实并非如此,因为条目是无序的。

至于“按其哈希值排序”:哈希值一般取自整数范围,但哈希映射中没有 2**32 个槽位。哈希值的范围将通过取模槽数来减少到槽数。此外,当您向哈希映射添加条目时,它可能会更改大小以适应新值。这可能会导致所有先前的条目被重新放置,从而改变它们的顺序。

在无序的数据结构中,您不能假设条目的顺序。

【讨论】:

  • 我想我可以假设它们按哈希值排序。
  • 是的,但它们仍然会按其哈希值排序。当然如果不同key的hash值相同,那么顺序是不确定的。
  • 它们不是“按哈希值排序的”。所有这一切都完全取决于实施。如果是开放散列,则如果它们没有引起冲突,则它们的散列值以素数为模,如果存在冲突,则它们的散列值以素数为模,以另一个素数为模。如果它是大多数情况下的链接/桶实现,桶按哈希值以桶数为模排序,列表中的项目可能在插入时间排序。您不能将其中的任何一个称为“按哈希值排序”,或者在任何有用的意义上都不能称为“排序”。
  • @Albert:如果哈希映射有 1009 个插槽(质数),并且您的两个值哈希到 13116 和 13118,它们最终将位于插槽 1008(13116 % 1009)和 1(13118) % 1009)。迭代条目时,第二个将首先出现。你不能假设无序地图中的排序。
  • 感谢这个例子。也感谢 EJP!这真的让我明白了。
【解决方案3】:

正如 unordered_map 的名字所暗示的,C++0x 标准没有指定排序。 unordered_map 的表观顺序取决于实际实现的方便程度。

【讨论】:

  • 为什么会这样?按哈希值排序不是很明显吗?
  • @Albert 没有什么说 unordered_map 必须使用散列。事实上,当考虑到冲突时,unordered_map 的顺序无法从哈希函数中预测。
  • @Albert:让实施者决定适合他们实施的最佳顺序。 unordered_map 不保证任何顺序,您不依赖它,实现者决定最佳顺序(如果有)以提供最佳性能;故事的结局。 C++ 标准的精神是要求最低限度并避免无用的约束,以让实现者提供他们所能提供的最佳性能。
  • 谁/为什么不赞成这个?对我来说,这是最符合标准的答案。
  • 好的,但是这个答案让你知道你可以依赖什么以及它背后的理由是什么:你不能依赖以任何方式订购它,就像让实施者在最好的。谈论实现的内部原理很有趣,但作为 unordered_map 的最终用户,您应该依赖的正是标准所说的。
【解决方案4】:

如果您想进行类比,请查看您选择的 RDBMS。

如果您在执行查询时没有指定 ORDER BY 子句,则返回的结果是“无序的”——也就是说,按照数据库感觉的任何顺序。没有指定顺序,系统可以随意“订购”它们以获得最佳性能。

【讨论】:

  • 它们真的是无序的吗?它们不会按哈希值排序吗?
  • 我不喜欢这个类比,因为在 unordered_map 中,顺序不是一些模糊的内部细节,而是哈希算法的结果。事实上如果你有一个最优的散列函数,那么在查找、插入和删除任意元素期间执行的操作数量并不取决于序列中的元素数量 (tiny.cc/vqm58)
【解决方案5】:

你是对的,unordered_map 实际上是哈希排序的。请注意,大多数当前实现(TR1 之前)将其称为 hash_map

IBM C/C++ 编译器documentation 指出,如果您有一个最佳散列函数,那么在查找、插入和删除任意元素期间执行的操作数不依赖于其中的元素数。序列,所以这意味着顺序不是那么无序......

现在,散列排序是什么意思?由于哈希应该是不可预测的,根据定义,您不能对地图中元素的顺序做出任何假设。这就是它在 TR1 中重新命名的原因:旧名称暗示了一个订单。现在我们知道实际使用了一个订单,但您可以忽略它,因为它是不可预测的。

【讨论】:

  • 呃,为什么这被否决了?在我看来,这是迄今为止最正确的答案。不是吗?请那些认为不是的人,添加一些 cmets。
  • 查看其他答案。一个非常常见的实现按hash(Key) % NumberOfBuckets 对键进行排序,这与按hash(Key) 排序绝对不同。重要的后果之一是,如果插入更多元素并且存储桶的数量增加,则顺序可能会发生变化。如果您错误地认为它是哈希排序的,那么添加更多元素时顺序不会改变。
  • @MSalters:这就是为什么我写你不必依赖任何哈希顺序,因为它是不可预测的。
  • 这可能是不可预测的,但是 - 如果事情如你所描述的那样 - 至少顺序会是稳定的。这意味着如果 a 现在在 b 之前,它将始终如此。现实与标准不一致。这对于在迭代容器时插入或删除元素的算法尤其重要。
猜你喜欢
  • 1970-01-01
  • 2016-07-23
  • 1970-01-01
  • 2019-06-25
  • 2013-06-11
  • 2011-08-18
  • 2012-10-08
  • 2014-08-02
  • 1970-01-01
相关资源
最近更新 更多