与std :: hash发生意外冲突

时间:2011-11-01 15:19:57

标签: c++ visual-studio-2010 hash hash-collision stdhash

我知道散列无限数量的字符串到32b int必须生成冲突,但我期望从散列函数中得到一些不错的分布。

这两个字符串具有相同的哈希值是不是很奇怪?

size_t hash0 = std::hash<std::string>()("generated_id_0");
size_t hash1 = std::hash<std::string>()("generated_id_1");
//hash0 == hash1

我知道我可以使用boost::hash<std::string>或其他人,但我想知道std::hash出了什么问题。我用错了吗?我不应该以某种方式“播种”它吗?

5 个答案:

答案 0 :(得分:22)

您对std::hash的使用没有任何问题。问题是与Visual Studio 2010捆绑在一起的标准库实现提供的特化std::hash<std::string>只接受字符串字符的子集来确定哈希值(可能是出于性能原因)。巧合的是,包含14个字符的字符串的最后一个字符不是此集合的一部分,这就是两个字符串产生相同散列值的原因。

据我所知,这种行为符合标准,要求只是对具有相同参数的散列函数的多次调用必须始终返回相同的值。但是,哈希冲突概率应该最小。 VS2010实现完成了强制性部分,但没有考虑可选部分。

有关详细信息,请参阅头文件xfunctional中的实现(从我的副本中的第869行开始)和C ++标准的§17.6.3.4(latest public draft)。

如果你绝对需要一个更好的字符串哈希函数,你应该自己实现它。它实际上是not that hard

答案 1 :(得分:9)

标准没有指定确切的哈希算法,因此结果如此 会有所不同。 VC10使用的算法似乎没有采用所有的算法 如果字符串超过10个字符,则考虑字符;它 增量为1 + s.size() / 10的进展。这是合法的, 虽然从QoI的角度来看,相当令人失望;这样的哈希码 众所周知,对于一些典型的数据集(如 网址)。我强烈建议你用FNV哈希或者替换它 一个基于梅森素数的人:

FNV哈希:

struct hash
{
    size_t operator()( std::string const& s ) const
    {
        size_t result = 2166136261U ;
        std::string::const_iterator end = s.end() ;
        for ( std::string::const_iterator iter = s.begin() ;
              iter != end ;
              ++ iter ) {
            result = (16777619 * result)
                    ^ static_cast< unsigned char >( *iter ) ;
        }
        return result ;
    }
};

Mersenne prime hash:

struct hash
{
    size_t operator()( std::string const& s ) const
    {
        size_t result = 2166136261U ;
        std::string::const_iterator end = s.end() ;
        for ( std::string::const_iterator iter = s.begin() ;
              iter != end ;
              ++ iter ) {
            result = 127 * result
                   + static_cast< unsigned char >( *iter ) ;
        }
        return result ;
    }
};

(据说FNV哈希值更好,但梅森素数哈希将是 在很多机器上运行得更快,因为经常乘以127 明显快于乘以2166136261。)

答案 2 :(得分:3)

您可能会获得不同的哈希值。我得到不同的哈希值(GCC 4.5):

hashtest.cpp

#include <string>
#include <iostream>
#include <functional>
int main(int argc, char** argv)
{
size_t hash0 = std::hash<std::string>()("generated_id_0");
size_t hash1 = std::hash<std::string>()("generated_id_1");
std::cout << hash0 << (hash0 == hash1 ? " == " : " != ") << hash1 << "\n";
return 0;
}

输出

# g++ hashtest.cpp -o hashtest -std=gnu++0x
# ./hashtest
16797002355621538189 != 16797001256109909978

答案 3 :(得分:2)

你没有种子散列函数,你最多可以加盐“它们”。

该功能以正确的方式使用,这种碰撞可能只是偶然的。

除非使用随机密钥执行大量测试,否则无法确定散列函数是否均匀分布。

答案 4 :(得分:0)

TR1哈希函数和最新标准为字符串之类的东西定义了正确的重载。当我使用std :: tr1 :: hash(g ++ 4.1.2)运行此代码时,我得到这两个字符串的不同哈希值。