Rabin-Karp算法

时间:2012-04-21 06:47:15

标签: c++ string performance algorithm rabin-karp

我感兴趣的是实现Rabin-Karp算法来搜索wiki:http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm中所述的子字符串。不是为了完成家庭作业,而是为了自身利益。我已经实现了Rabin-Karp算法(如下所示)并且它可以工作。但是,性能真的非常糟糕!我知道我的哈希函数是基本的。但是,似乎对strstr()的简单调用总是胜过我的函数rabin_karp()。我可以理解为什么 - 哈希函数比简单的char-by-char比较每个循环做更多的工作。我在这里错过了什么? Rabin-Karp算法应该比调用strstr()更快吗?何时最好使用Rabin-Karp算法?因此,我的自身利益。我甚至实现了算法吗?

size_t hash(char* str, size_t i)
{
   size_t h = 0;
   size_t magic_exp = 1;
// if (str != NULL)
   {
      while (i-- != 0)
      {
         magic_exp *= 101;
         h += magic_exp + *str;
         ++str;
      }
   }

   return h;
}

char* rabin_karp(char* s, char* find)
{
   char* p = NULL;

   if (s != NULL && find != NULL)
   {
      size_t n = strlen(s);
      size_t m = strlen(find);

      if (n > m)
      {
         size_t hfind = hash(find, m);

         char* end = s + (n - m + 1);
         for (char* i = s; i < end; ++i)
         {
            size_t hs = hash(i, m);
            if (hs == hfind)
            {
               if (strncmp(i, find, m) == 0)
               {
                  p = i;
                  break;
               }
            }
         }
      }
   }

   return p;
}

3 个答案:

答案 0 :(得分:12)

您尚未正确实现哈希。 Rabin-Karp的关键是逐步更新散列,因为潜在的匹配沿着要搜索的字符串移动。正如您所确定的,如果您重新计算每个潜在匹配位置的整个哈希值,事情将会非常缓慢。

对于除第一次比较之外的每种情况,您的散列函数应该使用现有散列,一个新字符和一个旧字符,并返回更新的散列。

答案 1 :(得分:4)

Rabin-Karp是一种滚动哈希算法 - 我们的想法是能够将子串一个位置移动到任一方向(左或右),并能够使用常数操作重新计算哈希值。正如您实现的那样,搜索具有复杂度O(N * L),其中N是大字符串的长度,L是您要搜索的字符串的长度。这是最天真的方法的复杂性,在我看来实际上是一点点pesimization。

为了改进算法预先计算magic_exp的指数并使用它们来“滚动”你的哈希 - 基本上就像你需要通过magic_exp减去最高度乘以的多项式一样,并将符号的哈希值添加到右边(用于移动)右边的哈希值。)

希望这有帮助。

答案 2 :(得分:1)

strstr正在使用KMP算法,该算法本质上也是线性的。这意味着两种算法的复杂性大致相同。从那时起,常数是重要因素。特别是在你有很多冲突的哈希函数错误的情况下,KMP会快得多。

编辑:还有一件事。对于Rabin Karp算法来说,预先计算前缀的所有哈希码是非常重要的。现在你没有实现正确的Rabin Karp,因为对函数的调用将是线性的,而不是复杂的常数。 (顺便说一下,维基百科不是很好的学习Rabin Karp的来源)。