字符串哈希
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<std::string>
有:
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<std::string>
在字符串长度上是 O(N) 。
std::unordered_set
和std::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]
是字符串的i
th字符,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 < _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<std::string>
或std::hash<int>
,并且实现在函数文件中(/path/to/c++.../include/c++/4.8/functional)
【讨论】:
看 Java 实现很有趣...谢谢!【参考方案4】:散列函数的复杂度永远不会是 O(1)。如果字符串的长度是 n,那么复杂度肯定是 O(n)。但是,如果您计算给定数组中的所有哈希值,则不必进行第二次计算,并且您始终可以通过比较预先计算的哈希值在 O(1) 时间内比较两个字符串。
【讨论】:
以上是关于字符串哈希的主要内容,如果未能解决你的问题,请参考以下文章