与std::hash发生意外冲突

Unexpected collision with std::hash

本文关键字:意外 冲突 hash std      更新时间:2023-10-16

我知道将无限数量的字符串散列成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有什么问题。我用错了吗?难道我不应该"播种"它吗?

您使用std::hash没有任何问题。问题是Visual Studio 2010附带的标准库实现提供的专门化std::hash<std::string>只接受字符串字符的子集来确定哈希值(可能是出于性能原因)。巧合的是,有14个字符的字符串的最后一个字符不属于这个集合,这就是为什么两个字符串产生相同的哈希值。

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

有关详细信息,请参阅c++标准的头文件xfunctional(在我的副本中从第869行开始)和§17.6.3.4中的实现(最新的公开草案)。

如果你绝对需要一个更好的字符串哈希函数,你应该自己实现它。其实没那么难。

标准中没有指定确切的哈希算法,因此结果会有所不同。VC10使用的算法似乎并没有把所有的如果字符串长度超过10个字符,则考虑字符;它以1 + s.size() / 10的增量前进。这是合法的,虽然从qi的角度来看,相当令人失望;这样的哈希码对于某些典型的数据集(例如url)。我强烈建议您将其替换为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 ;
    }
};

梅森素数哈希:

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经常明显快于乘以16777619)

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

<标题> hashtest.cpp h1> 输出
# g++ hashtest.cpp -o hashtest -std=gnu++0x
# ./hashtest
16797002355621538189 != 16797001256109909978

你没有种子哈希函数,你最多只能盐"他们"。

函数的使用方式正确,这种碰撞可能只是偶然的。

你无法判断哈希函数是否不是均匀分布的,除非你用随机键进行大量的测试。

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