对于 unordered_map,对于具有 3 个无符号字符和一个 int 的结构,啥是好的散列函数?

Posted

技术标签:

【中文标题】对于 unordered_map,对于具有 3 个无符号字符和一个 int 的结构,啥是好的散列函数?【英文标题】:What's a good hash function for struct with 3 unsigned chars and an int, for unordered_map?对于 unordered_map,对于具有 3 个无符号字符和一个 int 的结构,什么是好的散列函数? 【发布时间】:2012-11-15 00:13:33 【问题描述】:

我只想使用 unordered_map 和我的结构作为键,因为我不需要任何排序..但我就是找不到自己拥有所有这些散列的东西..

作为一个相关问题..当人们比较无序和有序映射时,他们从不谈论哈希函数,那怎么可能?一个糟糕的哈希函数不能让无序映射比映射慢吗? (完全归功于哈希函数)

struct exemple

  unsigned char a,b,c;
  unsigned int n;

  bool operator == ( const exemple & other) const ..
;

namespace std 
template <>
struct hash<exemple> : public std::unary_function<const exemple &, std::size_t>

    inline std::size_t operator()(const exemple & exemple_p ) const
    
        return 0;// what do I do
    
;

-编辑- a,b,c 只能有值 'a', 'b', 'c' 或 'd', n 在 3 到 60 之间变化。

【问题讨论】:

哈希函数需要自己写吗? 这是两个截然不同的问题。请一次发布其中一个。 @evanmcdonnal 你是什么意思?如果我不提供无序地图,则无法编译。 是的,但是您可以使用已经存在的哈希库中的逻辑。例如,假设我有一些字符串散列函数hash(string),我可以创建一个函数,将int 转换为字符串,然后将其与三个chars 连接,然后以return hash(StringIjustMade); 结束函数,这与实际上自己编写低级哈希逻辑。 首先删除unary_function。这些东西在官方上是没用的,因为就像永远一样。 【参考方案1】:

您在散列函数中执行的操作取决于您获得的值,而不一定取决于它们的类型。如果所有四个数据成员包含均匀分布的每个值,我会将这两个字符组合成一个 unsigned long 并返回两个值异或的结果:

typedef unsigned long ulong;
return n ^ (ulong(a << 16) | ulong(b << 8) | ulong(c));

肯定是一个散列函数。它是否运作良好是一个不同的问题。您也可以将结果与std::hash&lt;unsigned long&gt; 结合起来。

【讨论】:

适当组合哈希:为什么 C++11 省略了 hash_combine?这是boost::hash 的一个不错的功能。 哦,我明白了...我应该提一下,然后我的无符号字符只能采用 a、b、c 或 d 值...而 n 从 ~3 到 60 不等 你是说你有 43*60 ~= 2^12 == 4096 个值?在这种情况下,不要打扰使用哈希映射,而是使用数组...... @pmr:我能找到的hash_combine 的唯一踪迹是在n3333 中。换句话说:你没有提出来!其他人也没有。 我不知道你在尝试什么,但它应该可以工作:std::hash&lt;unsigned long&gt; hasher; unsigned long hash = hasher(value);。从外观上看,您尝试将unsigned long 作为构造函数参数传递给std::hash&lt;unsigned long&gt;,即std::hash&lt;unsigned long&gt;(value)。这当然行不通。【参考方案2】:

这是一个基线哈希函数:

unsigned long long h = (n << 24) | (a << 16) | (b << 8) | c;
return std::hash(h);

即,只需将成员打包到 unsigned long long,然后将工作卸载到 std::hash。在常见的情况下,int 是 32 位宽,long long 是 64 位,并且假设您的字符不是负数,这将使用您对象中的所有信息作为哈希值。

【讨论】:

【参考方案3】:

将您的 struct 作为一个整体考虑为一串字节(准确地说是 7 个)。您可以在这 7 个字节上使用任何可接受的通用字符串散列函数。这是应用于您的示例的 FNV (Fowler/Noll/Vo) 通用位串哈希函数(在给定的哈希函子类中):

inline std::size_t operator()(const exemple& obj ) const

  const unsigned char* p = reinterpret_cast<const unsigned char*>( &obj );
  std::size_t h = 2166136261;

  for (unsigned int i = 0; i < sizeof(obj); ++i)
    h = (h * 16777619) ^ p[i];

  return h;

请注意我如何将对 exemple 结构 (obj) 的引用转换为指向 const unsigned char 的指针,以便我可以一个接一个地访问该结构的字节,并将其视为不透明的二进制对象。请注意,sizeof(obj) 实际上可能是 8 而不是 7,具体取决于编译器的填充(这意味着结构中的某处有一个垃圾填充字节,可能在 cn 之间。如果你愿意,你可以重写散列函数迭代abc,然后是n的字节按顺序(或任何顺序),这将消除任何填充字节(可能存在也可能不存在)对struct 的哈希值。

是的,一个糟糕的哈希函数会使unordered_mapordered_map 慢。这并不总是被讨论,因为像上面给出的 FNV 哈希这样的通用快速算法被假定为使用unordered_map 的人使用,在这些情况下,通常unordered_mapordered_map 更快,但代价是按顺序迭代容器元素的能力。但是,是的,您必须对数据使用良好的散列函数,并且通常使用这些众所周知的散列之一就足够了。然而,归根结底,每个散列函数都有其弱点,具体取决于输入数据(这里是exemple 结构的内容)的分布。

可以在Eternally Confuzzled 找到关于广义散列和示例散列函数的精彩讨论,包括与我给您的类似的 C 样式 FNV 散列。

【讨论】:

【参考方案4】:

boost::hash_combine 就是为此目的而设计的:

std::size_t hash = 0;
for (const auto& value : a, b, c) 
    boost::hash_combine(hash, value);

boost::hash_combine(hash, n);
return hash;

【讨论】:

以上是关于对于 unordered_map,对于具有 3 个无符号字符和一个 int 的结构,啥是好的散列函数?的主要内容,如果未能解决你的问题,请参考以下文章

对于 std::tr1::unordered_map,是不是有任何等效的 std::algorithm 类似于 std::map::lower_bound?

unordered_map和map的区别

如何在 unordered_map 中插入 3 个元素或其他奇数个元素?

2019 ICPC上海网络赛 G. Substring 哈希+尺取法+unordered_map

std::unordered_map::extract 引用/指针失效

对于一颗具有n个结点,度为4的树来说,( )