Rabin Karp 字符串匹配算法

Posted

技术标签:

【中文标题】Rabin Karp 字符串匹配算法【英文标题】:Rabin Karp string matching algorithm 【发布时间】:2012-04-26 17:26:04 【问题描述】:

我在网站上的论坛中看到了这个 ,我有兴趣尝试实现它,但我想知道是否有人能告诉我为什么变量 ulong Q 和 ulong D 是 100007 和 256分别:S? 这些价值观有什么意义?

static void Main(string[] args)

    string A = "String that contains a pattern.";
    string B = "pattern";
    ulong siga = 0;
    ulong sigb = 0;
    ulong Q = 100007;
    ulong D = 256;
    for (int i = 0; i < B.Length; i++)
    
        siga = (siga * D + (ulong)A[i]) % Q;
        sigb = (sigb * D + (ulong)B[i]) % Q;
    
    if (siga == sigb)
    
        Console.WriteLine(string.Format(">>0<<1", A.Substring(0, B.Length), A.Substring(B.Length)));
        return;
    
    ulong pow = 1;
    for (int k = 1; k <= B.Length - 1; k++)
        pow = (pow * D) % Q;

    for (int j = 1; j <= A.Length - B.Length; j++)
    
        siga = (siga + Q - pow * (ulong)A[j - 1] % Q) % Q;
        siga = (siga * D + (ulong)A[j + B.Length - 1]) % Q;
        if (siga == sigb)
        
            if (A.Substring(j, B.Length) == B)
            
                Console.WriteLine(string.Format("0>>1<<2", A.Substring(0, j),
                                                                    A.Substring(j, B.Length),
                                                                    A.Substring(j + B.Length)));
                return;
            
        
    
    Console.WriteLine("Not copied!");

【问题讨论】:

我不熟悉这个算法。我得到了第一个包含错误的for () 循环。如果 B.Length > A.Length 怎么办? IndexOutOfRangeException 在阅读了***的文章 (en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) 后,我可以看到 Q 是一个用于散列子字符串的“大素数”。 D 似乎是一个比例因子,它认为每个新字符位置都是 256 的倍数,但这似乎假设没有字符值 >= 256。不过,我在这里根本不是专家。 D 为 256 本质上是向左移动 8 位,为下一个字符腾出空间(不过,这是一个以 ascii 为中心的视图) 【参考方案1】:

关于神奇的数字,保罗的回答很清楚。

就代码而言,Rabin Karp 的主要思想是在字符串的滑动部分和模式之间执行哈希比较。

不能每次都对整个子字符串计算哈希,否则计算复杂度将是二次的O(n^2),而不是线性的O(n)

因此,应用了rolling hash 函数,例如在每次迭代时只需要一个字符来更新子字符串的哈希值。

那么,让我们评论一下你的代码吧:

for (int i = 0; i < B.Length; i++)

    siga = (siga * D + (ulong)A[i]) % Q;
    sigb = (sigb * D + (ulong)B[i]) % Q;

if (siga == sigb)

    Console.WriteLine(string.Format(">>0<<1", A.Substring(0, B.Length), A.Substring(B.Length)));
    return;

^ 这部分计算模式B (sigb) 的哈希,以及A 的初始子串的哈希码,其长度与B 相同。 实际上它并不完全正确,因为 hash 可能会发生冲突¹,因此有必要修改 if 语句:if (siga == sigb &amp;&amp; A.Substring(0, B.Length) == B)

ulong pow = 1;
for (int k = 1; k <= B.Length - 1; k++)
    pow = (pow * D) % Q;

^ 这里计算出的pow 是执行滚动哈希所必需的。

for (int j = 1; j <= A.Length - B.Length; j++)

    siga = (siga + Q - pow * (ulong)A[j - 1] % Q) % Q;
    siga = (siga * D + (ulong)A[j + B.Length - 1]) % Q;
    if (siga == sigb)
    
        if (A.Substring(j, B.Length) == B)
        
            Console.WriteLine(string.Format("0>>1<<2", A.Substring(0, j),
                                                                A.Substring(j, B.Length),
                                                                A.Substring(j + B.Length)));
            return;
        
    

^ 最后,扫描剩余的字符串(即从第二个字符到结尾),更新 A 子字符串的哈希值,并与 B 的哈希值(在开头计算)进行比较。

如果两个哈希值相等,则比较子字符串和模式¹,如果它们实际上相等,则返回一条消息。


¹Hash values can collide;因此,如果两个字符串具有不同的哈希值,它们肯定不同,但如果两个哈希值相等,它们可以相等或不相等。

【讨论】:

非常感谢您通过评论代码来帮助我。非常感谢!【参考方案2】:

该算法使用散列进行快速字符串比较。 Q 和 D 是编码人员可能通过一些试验和错误得出的神奇数字,并为这个特定算法提供了一个很好的 distribution 哈希值。

您可以看到这些类型的幻数用于散列许多地方。下面的例子是.NET 2.0字符串类型的GetHashCode函数的反编译定义:

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override unsafe int GetHashCode()

    char* chrPointer = null;
    int num1;
    int num2;
    fixed (string str = (string)this)
    
        num1 = 352654597;
        num2 = num1;
        int* numPointer = chrPointer;
        for (int i = this.Length; i > 0; i = i - 4)
        
            num1 = (num1 << 5) + num1 + (num1 >> 27) ^ numPointer;
            if (i <= 2)
            
                break;
            
            num2 = (num2 << 5) + num2 + (num2 >> 27) ^ numPointer + (void*)4;
            numPointer = numPointer + (void*)8;
        
    
    return num1 + num2 * 1566083941;

下面是 R# 生成的示例类型的 GetHashcode 覆盖函数的另一个示例:

    public override int GetHashCode()
    
        unchecked
        
            int result = (SomeStrId != null ? SomeStrId.GetHashCode() : 0);
            result = (result*397) ^ (Desc != null ? Desc.GetHashCode() : 0);
            result = (result*397) ^ (AnotherId != null ? AnotherId.GetHashCode() : 0);
            return result;
        
    

【讨论】:

以上是关于Rabin Karp 字符串匹配算法的主要内容,如果未能解决你的问题,请参考以下文章

模式匹配:滚动哈希到 Rabin-Karp 算法

Rabin Karp 字符串匹配算法

除了 Knuth-Morris-Pratt、Rabin-Karp 等,还都有哪些可用的字符串匹配算法?

Rabin-Karp 字符串匹配不匹配

何时使用 Rabin-Karp 或 KMP 算法?

Rabin-Karp指纹字符串查找算法