Rabin-Karp 算法简介

Rabin-Karp 算法是由 Richard M. KarpMichael O. Rabin创建的字符串搜索算法,它使用散列来查找文本中的一组模式字符串中的任何一个。

字符串的子字符串是另一个出现的字符串。例如, verstackoverflow 的子字符串。不要与子序列混淆,因为 cover 是同一个字符串的子序列。换句话说,字符串中连续字母的任何子集都是给定字符串的子字符串。

在 Rabin-Karp 算法中,我们将生成我们正在寻找的模式的哈希,并检查我们的文本的滚动哈希是否与模式匹配。如果它不匹配,我们可以保证的是,模式不存在文本。但是,如果匹配,则模式可以出现在文本中。我们来看一个例子:

假设我们有一个文本: yeminsajid ,我们想知道文本中是否存在模式 nsa 。要计算散列和滚动散列,我们需要使用素数。这可以是任何素数。让我们在这个例子中使用 prime = 11 。我们将使用以下公式确定哈希值:

(1st letter) X (prime) + (2nd letter) X (prime)¹ + (3rd letter) X (prime)² X + ......

我们将表示:

a -> 1    g -> 7    m -> 13   s -> 19   y -> 25
b -> 2    h -> 8    n -> 14   t -> 20   z -> 26
c -> 3    i -> 9    o -> 15   u -> 21
d -> 4    j -> 10   p -> 16   v -> 22
e -> 5    k -> 11   q -> 17   w -> 23
f -> 6    l -> 12   r -> 18   x -> 24

nsa 的哈希值为 :

 14 X 11⁰ + 19 X 11¹ + 1 X 11² = 344

现在我们找到文本的滚动哈希值。如果滚动哈希与模式的哈希值匹配,我们将检查字符串是否匹配。因为我们的模式有 3 个字母,我们将采取 1 日 3YEM 从我们的文本,并计算哈希值。我们得到:

25 X 11⁰ + 5 X 11¹ + 13 X 11² = 1653

此值与我们的模式的哈希值不匹配。所以字符串在这里不存在。现在我们需要考虑下一步。计算下一个字符串 emi 的哈希值。我们可以使用我们的公式计算出来。但这将是相当微不足道的,并且会花费更多。相反,我们使用另一种技术。

  • 我们从当前哈希值中减去 Previous String第一个字母的值。在这种情况下, y 。我们得到了,1653 - 25 = 1628
  • 我们将差异除以我们的素数,这个例子是 11 。我们得到了,1628 / 11 = 148
  • 我们加上新的字母 X(素数)ᵐ-1 ,其中 m 是模式的长度,商是 i = 9 。我们得到了,148 + 9 X 11² = 1237

新的哈希值不等于我们的模式哈希值。继续前进,为 ñ 我们得到:

Previous String: emi
First Letter of Previous String: e(5)
New Letter: n(14)
New String: "min"
1237 - 5 = 1232
1232 / 11 = 112
112 + 14 X 11² = 1806

它不匹配。在那之后,对于 s ,我们得到:

Previous String: min
First Letter of Previous String: m(13)
New Letter: s(19)
New String: "ins"
1806 - 13 = 1793
1793 / 11 = 163
163 + 19 X 11² = 2462

它不匹配。接下来,对于 a ,我们得到:

Previous String: ins
First Letter of Previous String: i(9)
New Letter: a(1)
New String: "nsa"
2462 - 9 = 2453
2453 / 11 = 223
223 + 1 X 11² = 344

这是一个匹配! 现在我们将模式与当前字符串进行比较。由于两个字符串都匹配,因此子字符串存在于此字符串中。然后我们返回子字符串的起始位置。

伪代码将是:

哈希计算:

Procedure Calculate-Hash(String, Prime, x):
hash := 0                                  // Here x denotes the length to be considered
for m from 1 to x                          // to find the hash value
    hash := hash + (Value of String[m])ᵐ⁻¹
end for
Return hash

哈希重新计算:

Procedure Recalculate-Hash(String, Curr, Prime, Hash):
Hash := Hash - Value of String[Curr]  //here Curr denotes First Letter of Previous String
Hash := Hash / Prime
m := String.length
New := Curr + m - 1
Hash := Hash + (Value of String[New])ᵐ⁻¹
Return Hash

字符串匹配:

Procedure String-Match(Text, Pattern, m):
for i from m to Pattern-length + m - 1
    if Text[i] is not equal to Pattern[i]
        Return false
    end if
end for
Return true

拉宾,卡普:

Procedure Rabin-Karp(Text, Pattern, Prime):
m := Pattern.Length
HashValue := Calculate-Hash(Pattern, Prime, m)
CurrValue := Calculate-Hash(Pattern, Prime, m)
for i from 1 to Text.length - m
    if HashValue == CurrValue and String-Match(Text, Pattern, i) is true
        Return i
    end if
    CurrValue := Recalculate-Hash(String, i+1, Prime, CurrValue)
end for
Return -1

如果算法找不到任何匹配,则只返回 -1

该算法用于检测抄袭。在给定源材料的情况下,该算法可以快速在纸张中搜索来自源材料的句子实例,忽略诸如大小写和标点符号之类的细节。由于所搜索的字符串丰富,单字符串搜索算法在这里是不切实际的。同样, Knuth-Morris-Pratt 算法Boyer-Moore 字符串搜索算法是比 Rabin-Karp 更快的单模式字符串搜索算法。但是,它是多模式搜索的首选算法。如果我们想在文本中找到任何大数,比如 k,固定长度模式,我们可以创建一个简单的 Rabin-Karp 算法变体。

对于组合长度为 m 的长度为 np 的模式的文本,其平均和最佳情况运行时间在空间 O(p)O(n + m ,但其最坏情况时间为 O(nm) 。 **** **** **** ****