【问题标题】:Are there any working implementations of the rolling hash function used in the Rabin-Karp string search algorithm?Rabin-Karp 字符串搜索算法中使用的滚动哈希函数是否有任何工作实现?
【发布时间】:2011-01-19 19:53:12
【问题描述】:

我希望使用滚动散列函数,这样我就可以对一个非常大的字符串的 n-gram 进行散列。

例如:

“stackoverflow”,分成 5 克是:

"stack", "tacko", "ackov", "ckove", “kover”、“overf”、“verfl”、“erflo”、“rflow”

这对于滚动散列函数是理想的,因为在我计算了第一个 n-gram 散列之后,以下的计算相对便宜,因为我只需删除第一个散列的第一个字母并添加新的最后一个字母第二个哈希。

我知道通常这个哈希函数是这样生成的:

H = c1ak − 1 + c2ak − 2 + c3ak − 3 + ... + cka0 其中 a 是常数,c1,.. .,ck 是输入字符。

如果您点击 Rabin-Karp string search algorithm 上的此链接,它会指出“a”通常是一些大素数。

我希望我的哈希值存储在 32 位整数中,那么“a”应该有多大的素数,这样我的整数才不会溢出?

在某处我已经可以使用此哈希函数的现有实现吗?


这是我创建的一个实现:

public class hash2
{

    public int prime = 101;

    public int hash(String text)
    {
        int hash = 0;

        for(int i = 0; i < text.length(); i++)
        {
            char c = text.charAt(i);
            hash += c * (int) (Math.pow(prime, text.length() - 1 - i));
        }

        return hash;
    }

    public int rollHash(int previousHash, String previousText, String currentText)
    {

        char firstChar = previousText.charAt(0);
        char lastChar = currentText.charAt(currentText.length() - 1);

        int firstCharHash = firstChar * (int) (Math.pow(prime, previousText.length() - 1));
        int hash = (previousHash - firstCharHash) * prime + lastChar;

        return hash;
    }

    public static void main(String[] args)
    {
        hash2 hashify = new hash2();

        int firstHash = hashify.hash("mydog");
        System.out.println(firstHash);
        System.out.println(hashify.hash("ydogr"));
        System.out.println(hashify.rollHash(firstHash, "mydog", "ydogr"));
    }

}

我使用 101 作为我的素数。我的哈希值是否会溢出有关系吗?我认为这是可取的,但我不确定。

这似乎是解决这个问题的正确方法吗?

【问题讨论】:

  • 为什么这个应用程序的素数与“正常”字符串哈希码生成有什么不同?
  • 算法很简单,很容易用伪代码实现。你试过自己编码吗?

标签: c# java algorithm hash rabin-karp


【解决方案1】:

据我了解,这是一个函数最小化:

2^31 - sum (maxchar) * A^kx

其中maxchar = 62(对于A-Za-z0-9)。我刚刚通过 Excel 计算了它(确切地说是 OO Calc):) 它找到的最大 A 是 76 或 73,对于素数。

【讨论】:

    【解决方案2】:

    我记得一个略有不同的实现,它似乎来自 sedgewick 的算法书籍之一(它还包含示例代码 - 尝试查找它)。这是调整为 32 位整数的摘要:

    您使用模运算来防止您的整数在每次操作后溢出。

    初始设置:

    • c = 文本(“stackoverflow”)
    • M = “n-gram”的长度
    • d = 字母表的大小 (256)
    • q = 一个大素数,这样 (d+1)*q 就不会溢出(8355967 可能是一个不错的选择)
    • dM = dM-1 mod q

    先计算第一个n-gram的hash值:

    h = 0
    for i from 1 to M:
      h = (h*d + c[i]) mod q
    

    并且对于以下每个 n-gram:

    for i from 1 to lenght(c)-M:
      // first subtract the oldest character
      h = (h + d*q - c[i]*dM) mod q
    
      // then add the next character
      h = (h*d + c[i+M]) mod q
    

    在减去最旧的字符之前必须添加 d*q 的原因是因为之前的模运算导致的小值可能会导致您遇到负值。

    包括错误,但我认为您应该明白这一点。尝试查找 sedgewick 的算法书籍之一以获取详细信息、更少的错误和更好的描述。 :)

    【讨论】:

    • 包含错误是什么意思?如果我这样做,我会遇到“负值”吗?如何预防?
    • @Myth17:我的意思是你应该谨慎使用我的(伪)代码,因为它可能包含错误/我没有对它进行广泛的测试。
    • Rabin-Karp 字符串搜索算法中使用的滚动哈希应该允许下一个哈希值计算为:s[i+1..i+m] = s[i. .i+m-1] - s[i] + s[i+m]。您提供的算法不能用于此目的。
    【解决方案3】:

    不确定您的目标是什么,但如果您想提高性能,使用 math.pow 的成本将远远超过计算滚动哈希值所节省的成本。

    我建议你从保持简单和高效开始,你很可能会发现它已经足够快了。

    【讨论】:

    • 计算幂的最快方法?
    • 视情况而定。普通乘法通常更快。
    猜你喜欢
    • 2021-12-25
    • 2012-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-27
    • 2013-12-23
    • 2023-03-09
    • 2011-12-11
    相关资源
    最近更新 更多