字符串算法之哈希

1.定义

我们定义一个把字符串映射到整数的函数 \(f\),这个 \(f\) 称为是 Hash 函数。

我们希望这个函数 \(f\) 可以方便地帮我们判断两个字符串是否相等。

词汇“映射”

映射意为将一个集合中的任意元素和另一个集合中的元素一一对应。(请大佬指正)

2.思想

Hash 的核心思想在于,将输入映射到一个值域较小、可以方便比较的范围,也就是为了让两个字符串更加容易比较(感谢大佬yeyou26的指正)。
Warning
这里的「值域较小」在不同情况下意义不同。

3.性质

具体来说,哈希函数最重要的性质可以概括为下面两条:

\(1.\)在 Hash 函数值不一样的时候,两个字符串一定不一样;

\(2.\)在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。

我们将 Hash 函数值一样但原字符串不一样的现象称为哈希碰撞。

引申:哈希碰撞

例子: $ K_{i} \neq K_{j} $
$ Hash(K_i)=Hash(K_j)$
该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

如何计算哈希

Warning:以下数学表达式较多,但有通俗易懂的解释,不要慌张。

计算哈希我们通常采用的是多项式 Hash 的方法,但是本蒟蒻并不会使用第一种,故使用第二种。

公式:

\[f(x)=\sum_{i=1}^l s_i \times b^{i-1} (mod~m) \]

看公式,求和 \([1,l]\) \(s_i*b^{i-1}\) ,所以xyz的哈希值为\(x+yb+zb^2\)

我们假设 \(b\)\(3\),则有

\(i\)\(1\) 时,处理 'x',由于 \(s_i \times b^{i-1}\),而 \(i=1\),故值为 \(0\)

\(i\)\(2\) 时,处理 'y',由于 \(s_i \times b^{i-1}\),而 \(i=2\),故值为 \(1\)

\(i\)\(3\) 时,处理 'z',由于 \(s_i \times b^{i-1}\),而 \(i=3\),故值为 \(2\)

1+2=3,所以该字符串哈希值为3。

当然,最后结果还要 \(Mod\)(模)\(M\)

根据上方一次酣畅淋漓地推理,相信你已经看懂了过程,接下来讲述如何选择公式中的b与M

这里 \(M\) 需要选择一个素数(至少要比最大的字符要大),\(b\) 可以任意选择。因为如果 \(M\) 不为素数,哈希碰撞(上文提到过)的可能性会大大提升。

Hash 的准确率

懒得写了qwq(bushi)

管他干嘛?

实现

效率低下但容易理解的版本

int gethash(string s)
int res=0;
for(int i=0;i<s.size();i++)
res = ((long long)res * B + s[i]) % M;
return res;

这个程序最关键的一步就是res = ((ll)res * B + s[i]) % M;套公式
当然我不是那种写显然的人,但剩下步骤真的很好理解!!!

3.哈希的改进

错误率
首先我们从碰撞率这一方面进行优化.
假定哈希函数将字符串随机地映射到大小为 M 的值域中,总共有 n 个不同的字符串,在随机数据下,若 \(M=10^9+7,n=10^6\),未出现碰撞的概率是极低的。
解决方案:
进行字符串哈希时,经常会对两个大质数分别取模,这样的话哈希函数的值域就能扩大到两者之积,错误率就非常小了。

时间复杂度方面
单次计算一个字符串的哈希值复杂度是 \(\mathcal{O}(n)\),其中 \(n\) 为串长。一般采取的方法是对整个字符串先预处理出每个前缀的哈希值,将哈希值看成一个 \(b\) 进制的数对 \(M\) 取模的结果,这样的话每次就能快速求出子串的哈希了.

4.应用

1.字符串匹配

求出模式串的哈希值后,求出文本串每个长度为模式串长度的子串的哈希值,分别与模式串的哈希值比较即可。
我们举个栗子
有原串 ABCACDAFEABA
有匹配串(给出让我们找的串)三个 (ACDA),(ABCD),(AFEAB)

未完待续......

posted @ 2024-02-20 15:02  March7thDev  阅读(116)  评论(0)    收藏  举报