字符串哈希

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字符串哈希相关的知识,希望对你有一定的参考价值。

字符串哈希

临时抱佛脚产物

字符串哈希的含义: 给字符串一个固定的编码, 使相同的字符串相同, 不同的字符串不同

大概就是, 把一个字符串转化为base进制

我们假设 base=11

则字符串123

转化为 \\(3+ 2* 11+ 1* 11* 11= 146\\)

同理单词也可以这样转换。

//这里base取233
//ha数组求的是hash值, B数组求的是base的次方, 后面会提到有什么用
    int lena= strlen(a+ 1);
    for(int i= 1; i<= lena; i++ )
    {
        ha[i]= ha[i- 1]* 233+ a[i]- \'a\';
        B[i]= B[i- 1]* 233;
    }

那么我们如何查询区间 \\([l, r]\\) 的哈希值呢

举个栗子

a[]= 123456, l= 3, r= 5, base= 10
\\(l\\)\\(r\\) 的哈希值就是 \\(345\\)

也就是 \\(12345- 123* 100\\)

很明显就可以发现 [l, r]= has[r]- has[l- 1]* B[r- l+ 1]

int has(int l, int r)
{
    int tmp= ha[r]- ha[l- 1]* B[r- l+ 1];
    return tmp;
}

然后我们就可以发现一个大问题, 这个时候long long大概是存不下的。

然后就到了第二个问题, 如何应对冲突


long long存不下, 很明显可以感觉到, 我们mod p就好了啊 (然而AcWing这道题直接让它自己溢出了, 不过必要得时候还是要mod的, 因为有时候会有条款限制

然后大问题来了, 有可能mod p后不满足我们上述的定义了啊

这就没办法了 (双哈希什么的我不会qaq),所以我们取好base就行了

y总可能喜欢131或13331 玄学冲突可能性低一点。

而其实什么都可以的啦, 只是mod不要开小!!!!!!小了都是白搭。

个人233算了qaq

//P就是要mod的
    int lena= strlen(a+ 1);
    for(int i= 1; i<= lena; i++ )
    {
        ha[i]= ha[i- 1]* 233+ a[i]- \'a\';
        ha[i]= ha[i]% P; B[i]= B[i- 1]* 233% P;
    }
int has(int l, int r)
{
    int tmp= ha[r]- ha[l- 1]* B[len]% P;    //注意要特判一下
    if(tmp< 0) return tmp+ P;
    else return tmp;
}
未来可期。

在哈希表中创建字符串的哈希值的时间复杂度

【中文标题】在哈希表中创建字符串的哈希值的时间复杂度【英文标题】:Time complexity of creating hash value of a string in hashtable 【发布时间】:2015-10-11 12:48:40 【问题描述】:

通常说在哈希表中插入和查找字符串是 O(1)。但是字符串的哈希键是如何产生的呢?为什么不考虑 O(L),字符串的长度? 我很清楚为什么整数是 O(1),而不是字符串。

我确实理解为什么通常插入哈希表是 O(1),但我对将哈希插入表之前的步骤感到困惑:生成哈希值。

在 java 中如何生成字符串的哈希键和在 C++ 中如何生成 unordered_map 之间也有什么区别吗? 谢谢。

【问题讨论】:

为什么关心字符串的长度,却忽略整数的位数? 啊,即使没有任何上下文也具有普遍意义的神奇“O(1)”。 @Matt,由于当数字可以适合 32 位或 64 位时,CPU 可以在 O(1) 内完成大部分操作。此外,大多数时候我们有长字符串,而不是大整数。 (尤其是编程比赛!) 我认为您不太了解 O(1) 在这种情况下的含义。散列一个键所花费的时间与散列表的当前大小完全没有关系。 Java 字符串在第一次计算后缓存其哈希码,因此您不必再次计算。 【参考方案1】:

在哈希表中插入等是 O(1),因为它在 表中元素的数量中是恒定的

在这种情况下,“O(1)”没有声称你可以多快地计算你的哈希值。如果为此付出的努力以某种方式增长,那就是它的方式。但是,我发现一个体面的(即“适合此应用程序”)散列函数的复杂性不太可能比散列对象的“大小”(即我们的字符串示例中的长度)的线性更差。

【讨论】:

那么,有什么方法可以让我意识到在 C++ 和 Java 中计算哈希的速度有多快?理论上(以及编程竞赛和面试题!),它可以在分析算法的时间复杂度方面产生影响。 @MehrdadAP 至少在 C++ 中,不是不看你的哈希函数的实现。但是,我希望为此目的的每个合理的散列函数在它散列的对象的“长度”或“大小”(无论这对您正在散列的对象意味着什么)中都具有线性复杂性。尽管我可以想象在某些情况下“较慢”的哈希值由于某种原因具有优势。 @MehrdadAP 不会用 C++ 说话,但 Java 哈希值是 O(N),N 取决于字符串的大小。在 C++ 中,大多数时候没有散列。例如,std::map 通常是一棵红黑树。 @user4581301 好吧,std::map 确实不是哈希表。例如,std::unordered_set 将使用哈希。 鲍姆米特奥根点。 @MehrdadAP 我有一位教练曾经这样解释过:“可能是 O(1),但 1 仍然可能需要一百万年。”【参考方案2】:

通常说在哈希表中插入和查找字符串是 O(1)。但是字符串的哈希键是如何产生的呢?为什么不是 O(L),字符串的长度?我很清楚为什么整数是 O(1),而不是字符串。

通常引用的 O(1) 表示时间不会随着容器中元素的数量而增长。正如您所说,从字符串生成哈希值的时间本身可能不是 O(1) 字符串长度 - 尽管对于某些实现它是:例如 Microsoft 的 C++ std::hash&lt;std::string&gt;有:

            size_t _Val = 2166136261U;
            size_t _First = 0;
            size_t _Last = _Keyval.size();
            size_t _Stride = 1 + _Last / 10;

            if (_Stride < _Last)
                    _Last -= _Stride;
            for(; _First < _Last; _First += _Stride)
                    _Val = 16777619U * _Val ^ (size_t)_Keyval[_First];
            return (_Val);

_Stride 是字符串长度的十分之一,因此 固定 个相距很远的字符将包含在哈希值中。这样的哈希函数在字符串长度上是O(1)

GCC 的 C++ 标准库采用了不同的方法:至少在 v4.7.2 中,它通过 _Hash_impl 支持类调用 static 非成员函数 _Hash_bytes,该函数执行包含每个字节的 Murmur 哈希.因此,GCC 的 hash&lt;std::string&gt; 在字符串长度上是 O(N)

GCC 对冲突最小化的更高优先级也体现在它对std::unordered_setstd::unordered_map 使用质数的桶中,MS 的实现没有这样做 - 至少在 VS2013/VC12 之前;总而言之,MS 的方法对于不易发生冲突且负载系数较低的键来说会更轻/更快,但会更早且更大幅度地降级。

Java 中的 hashTable 和 C++ 中的 unordered_map 生成字符串哈希键的方式有什么区别?

C++ 标准没有指定字符串的散列方式 - 由各个编译器实现决定。因此,不同的编译器会做出不同的妥协——甚至是同一编译器的不同版本。

文档 David Pérez Cabrera 的答案链接解释了 Java 中的 hashCode 函数:

返回此字符串的哈希码。 String 对象的哈希码计算为

 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

使用int算术,其中s[i]是字符串的ith字符,n是字符串的长度,^表示求幂。 (空字符串的哈希值为零。)

字符串的长度显然是 O(N)。

快速返回...

通常说在哈希表中插入和查找字符串是 O(1)。

...一个“关键”;-P 洞察力是,在许多问题领域中,已知字符串的实际长度不会有显着变化,或者最坏情况长度的散列仍然足够快。考虑一个人或公司的名称、街道地址、来自某些源代码的标识符、编程语言关键字、产品/书籍/CD 等名称:您可以预期十亿个键需要大约一百万倍的内存来存储第一千。使用哈希表,对整个数据集的大多数操作预计会花费一百万倍的时间。这将在 100 年后和今天一样真实。重要的是,如果某些请求与单个密钥相关,则执行时间不会比过去使用一千个密钥的时间长(假设有足够的 RAM,并忽略 CPU 缓存影响)——当然,如果它是一个长密钥它可能需要比短键更长的时间,如果您有超低延迟或硬实时要求,您可能会关心。但是,尽管拥有一百万倍的数据,使用随机键的请求的平均吞吐量将保持不变。

仅当您的问题域的密钥大小差异很大,并且考虑到您的性能需求,密钥散列时间很重要,或者您预计平均密钥大小会随着时间的推移而增加(例如,如果密钥是视频流,并且每隔几年人们就会提高分辨率和帧速率,从而导致密钥大小呈指数增长),您是否需要密切关注散列(和密钥比较)成本。

【讨论】:

"_Stride 是字符串长度的十分之一,所以固定数量的相距很远的字符将被包含在哈希值中。这样的哈希函数在长度上是 O(1)细绳。”我想说正确的时间复杂度,在这种情况下,是 O(log N),N 是字符串的长度。不是吗? @AliSalehi:很抱歉,但不是这样。如果 N 是字符串的长度,并且您只需沿字符串选择 10 个位置,那么字符串的长度是 10 个字符还是 1000 万个字符都没有关系 - 您可以直接访问这 10 个字符而无需遍历其余字符,由于内存是随机访问的。无论字符串的长度如何,都有一个固定的上层处理工作,因此 O(1)。 是的,我认为在你从函数中复制的 for 循环中:for(; _First &lt; _Last; _First += _Stride) 它会以 10 的步幅一直上升到 _Last,我没有注意_Stride = 1 + _Last / 10; @AliSalehi 是的 - 步幅是 N/10。但是,即使它跨 10 个字符,那也是 O(N) 而不是 O(log N)。例如,要散列一个 10,000 个字符的字符串,您仍然会比处理 10 个字符的字符串多做一千倍的工作 - 随着字符串长度的增加,它以相同的 10,000:10 = 1000:1 的比例增加,因此 O (N)。【参考方案3】:

根据Java的实现,Hashtable使用key(String或Integer)的hashCode方法。 Hashtable String.hashCode Integer.hashCode

而C++根据http://en.cppreference.com/w/cpp/utility/hash使用std::hash&lt;std::string&gt;std::hash&lt;int&gt;,并且实现在函数文件中(/path/to/c++.../include/c++/4.8/functional)

【讨论】:

看 Java 实现很有趣...谢谢!【参考方案4】:

散列函数的复杂度永远不会是 O(1)。如果字符串的长度是 n,那么复杂度肯定是 O(n)。但是,如果您计算给定数组中的所有哈希值,则不必进行第二次计算,并且您始终可以通过比较预先计算的哈希值在 O(1) 时间内比较两个字符串。

【讨论】:

以上是关于字符串哈希的主要内容,如果未能解决你的问题,请参考以下文章

URL片段的最大长度(哈希)

URL的PHP​​和哈希/片段部分

从 URL 获取片段(哈希“#”后的值)[关闭]

带有哈希片段的锚未导航到匹配的 id

一致性哈希算法PHP测试片段

如何从 URL 获取片段标识符(哈希 # 后的值)?