用c++创建一个字符串形式的哈希代码

creating a hash code form a string in c++

本文关键字:字符串 代码 哈希 一个 创建 c++      更新时间:2023-10-16

我有一个很长的字符串,需要进行比较以获得相等性。由于逐字符比较非常耗时,我喜欢为字符串创建一个哈希。

我喜欢生成的哈希代码是唯一的(或者生成两个具有相同哈希的字符串的可能性非常小)。我认为从一个字符串中创建一个int作为hash还不足以消除两个不同字符串具有相同hash代码的情况,所以我正在寻找一个字符串hash代码。

以上的假设我是对的吗?

为了澄清,假设我有一个长度为1K的字符串,并且我创建了一个10个字符的哈希代码,那么比较哈希代码的速度会提高100倍。

我的问题是如何在c++中创建这样的哈希代码?

我正在使用visualstudio2012在windows上进行开发。

要在这种情况下发挥作用,哈希代码必须快速计算使用比最大的单词更大的单词硬件支持的(通常为64位)可能是计数器多产的不过,你可以试一试。我找到了以下操作相当好:

unsigned long long
hash( std::string const& s )
{
    unsigned long long results = 12345; //  anything but 0 is probably OK.
    for ( auto current = s.begin(); current != s.end(); ++ current ) {
        results = 127 * results + static_cast<unsigned char>( *current );
    }
    return results;
}

使用这样的散列可能不是有利的,但是,除非大多数比较都使用不相等,但有很长的公共初始序列。回想起如果hashes相等,您仍然需要比较字符串,并且该比较只需要进行到第一个不相等的字符。(事实上,大多数比较我见过的函数从比较长度开始,只比较如果字符串长度相等,则为个字符。)

您可以使用许多哈希算法。

如果你想自己实现一个,那么一个简单的方法可以是为每个字符取ascii,并将其与0对齐(即a=1,b=2…),然后将其与字符串中的字符索引相乘。继续添加这些值,并将其存储为特定字符串的哈希值。

例如,abc的哈希值为:

HASH("abc") = 1*1 + 2*2 + 3*3 = 14; 

碰撞的概率随着字符串长度的增加而降低(考虑到您的字符串会很长)。

有许多已知的哈希算法可用。例如MD5、SHA1等。您不需要实现自己的算法,而是使用可用的算法之一。使用您选择的搜索引擎来查找像这样的实现。

这实际上取决于您的硬需求是什么。如果你有硬性要求,比如"搜索可能永远不会超过某个时间",那么可能没有适用的解决方案。如果你的目的只是为了加快大量搜索的速度,那么一个简单的短散列就可以了。

虽然将1000个字符的字符串散列为整数(单个32位或64位数字)通常会产生冲突,并且最终,但这并不值得担心
10个字符的散列也会产生冲突。这是1000>10这一事实的必然结果。对于每10个字符的散列,存在100个1000个字符串1

重要的问题是你是否真的会看到碰撞,你会多久看到一次,以及这是否重要。是否(或发生冲突的可能性)取决于而不是字符串的长度,而是不同字符串的数量
如果使用32位散列对77100个字符串(长度超过4个字符)进行散列,则每个新散列都有50%的机会遇到冲突。在25000个字符串的情况下,可能性只有5-6%左右。在1000个字符串中,可能性约为0.1%。
请注意,当我说"77100个字符串的50%"时,这并不意味着您实际遇到碰撞的几率那么高。这只是拥有两个具有相同哈希值的字符串的机会。除非大多数琴弦都是这样,否则真正击中一根琴弦的几率要低得多。

这意味着,对于大多数用例来说,这并不重要。除非你想散列成千上万的字符串,否则现在不要担心,使用32位散列
否则,除非你想散列数十亿个字符串,否则不要在这里担心,使用64位散列。

问题是,您必须准备好在任何情况下处理冲突,因为只要您有2个字符串,冲突的可能性就永远不会完全为零。原则上,即使只将2或3个1000个字符的字符串散列为500字节的散列,也可能发生冲突(可能性很小,但很可能)
这意味着,如果哈希在任何一种情况下都匹配,无论你的哈希有多长(或好坏),你都必须进行字符串比较。

如果碰撞不是每次都发生,那么它们就完全无关紧要了。如果您的表中有很多冲突,并且遇到了一个冲突,比如说,在10000次查找中有1次发生冲突(这是很多!),则不会产生实际影响。是的,每10000次查找中就必须进行一次无用的字符串比较,但其他9999次仅通过比较单个整数来完成。除非您有严格的实时要求,否则可测量的影响完全为零
即使你完全搞砸了,每5次搜索就遇到一次碰撞(非常不稳定的情况下,这意味着大约有8亿个字符串对发生碰撞,这只有在至少16亿个字符串的情况下才有可能发生),这仍然意味着五分之四的搜索没有发生碰撞,所以你仍然会在不进行比较的情况下丢弃80%的不匹配。

另一方面,生成一个10个字符的哈希既麻烦又缓慢,而且您可能会创建一个哈希函数,该函数的冲突比现有的32或64位哈希多(因为设计不好)
加密散列函数当然更好,但它们的运行速度也比非加密散列函数慢,而且存储16或32字节散列值所需的存储空间也大得多(对大多数人来说几乎没有好处)。这是一个空间/时间的权衡。

就我个人而言,我只想使用类似djb2的东西,它可以用3行C代码实现,运行良好,运行速度非常快。当然,还有许多其他散列函数可以使用,但我喜欢djb2,因为它很简单。

有趣的是,在阅读了James Kanze的答案后,发布的代码似乎是djb2的变体,只是有不同的种子和不同的乘数(分别为5381和33)
在相同的答案中,关于首先比较字符串长度的注释也是一个很好的提示。值得注意的是,您也可以将字符串的长度视为"哈希函数"的一种形式(尽管这是一种相当弱的函数,但它通常是"免费的")。


1然而,字符串并不像哈希那样是一些"随机二进制垃圾"。它们是结构化的低熵数据。到目前为止,这种比较并不成立。

好吧,我首先比较字符串长度。如果它们匹配,那么我会开始使用一种算法进行比较,该算法使用随机位置来测试字符的相等性,并在第一个差异处停止。随机位置将从字符串长度大小的向量中获得,该向量填充有从0到字符串长度-1的随机整数。不过,我还没有衡量过这种方法,这只是一个想法。但这将省去散列冲突的顾虑,同时减少比较时间。