如何找到不同的子串?

时间:2015-04-08 14:06:50

标签: c++ algorithm hash

给定一个字符串和一个固定长度l,我如何计算长度为l的不同子串的数量? 字符集的大小也是已知的。 (表示为s) 例如,给定一个字符串“PccjcjcZ”,s = 4,l = 3, 然后有5个不同的子串: “PCC”; “CCJ”; “CJC”; “JCJ”; “JCZ”

我尝试使用哈希表,但速度仍然很慢。 实际上我不知道如何使用字符大小。 我做过这样的事情

int diffPatterns(const string& src, int len, int setSize) {
  int cnt = 0;
  node* table[1 << 15];
  int tableSize = 1 << 15;
  for (int i = 0; i < tableSize; ++i) {
    table[i] = NULL;
  }

  unsigned int hashValue = 0;

  int end = (int)src.size() - len;

  for (int i = 0; i <= end; ++i) {
    hashValue = hashF(src, i, len);
    if (table[hashValue] == NULL) {
      table[hashValue] = new node(i);
      cnt ++;
    } else {
      if (!compList(src, i, table[hashValue], len)) {
        cnt ++;
      };
    }
  }

  for (int i = 0; i < tableSize; ++i) {
    deleteList(table[i]);
  }

  return cnt;
}

6 个答案:

答案 0 :(得分:2)

Hastables很好且实用,但请记住,如果子串的长度是L,并且整个字符串长度是N,则算法是Theta((N + 1-L)* L),即Theta( NL)对于大多数L.记住,只计算哈希需要Theta(L)时间。另外可能会发生碰撞。

可以使用后缀树,并提供有保证的O(N)时间算法(深度为L或更大的路径计数),但实现很复杂。保存优雅是你可以用你选择的语言找到现成的实现。

答案 1 :(得分:1)

使用哈希表的想法很好。它应该运作良好。

将您自己的哈希表实现为长度为2 ^ 15的数组的想法很糟糕。请改为Hashtable in C++?

答案 2 :(得分:0)

您可以使用unorder_set并将字符串插入集合中,然后获取集合的大小。由于集合中的值是唯一的,因此将不包括与先前找到的子字符串相同的子字符串。这应该使您接近O(StringSize - SubstringSize)复杂性

#include <iostream>
#include <string>
#include <unordered_set>


int main()
{
    std::string test = "PccjcjcZ";
    std::unordered_set<std::string> counter;
    size_t substringSize = 3;
    for (size_t i = 0; i < test.size() - substringSize + 1; ++i)
    {
        counter.insert(test.substr(i, substringSize));
    }

    std::cout << counter.size();

    std::cin.get();
    return 0;
}

答案 3 :(得分:0)

Veronica Kham对此问题的回答很好,但我们可以将此方法改进为预期的O(n),并且仍然使用简单的哈希表而不是后缀树或任何其他高级数据结构。

哈希函数

XY是长度为L的两个相邻子字符串,更准确地说是:

X = A[i, i + L - 1]

Y = B[i + 1, i + 1 + L - 1]

让我们的字母表中的每个字母分配一个非负整数,例如a := 1, b := 2,依此类推。

现在让我们定义一个哈希函数h

h(A[i, j]) := (P^(L-1) * A[i] + P^(L-2) * A[i + 1] + ... + A[j]) % M

其中P是理想情况下大于字母大小的素数,M是一个非常大的数字,表示不同可能的哈希数,例如,您可以将M设置为最大值您的系统中可用unsigned long long int

算法

关键的观察结果如下:

  

如果您为X计算了哈希值,则可以计算Y中的哈希值   O(1)时间。

假设我们计算了h(X),这显然可以在O(L)时间内完成。我们想要计算h(Y)。请注意,由于XY仅相差2个字符,因此我们可以使用加法和乘法轻松完成此操作:

h(Y) = ((h(X) - P^L * A[i]) * P) + A[j + 1]) % M

基本上,我们减去字母A [i]乘以h(X)中的系数,将结果乘以P,以便为其余字母获得适当的系数,最后,我们正在添加最后一个字母A[j + 1]

请注意,我们可以在开始时预先计算P的权限,然后我们可以模拟M

由于我们的散列函数返回整数,我们可以使用任何散列表来存储它们。请记住使所有计算以M为模,并避免整数溢出。

碰撞

当然,可能会发生碰撞,但由于P是素数且M非常庞大,因此这是一种罕见的情况。

如果要降低碰撞的概率,可以使用两种不同的散列函数,例如在每种函数中使用不同的模数。如果碰撞的概率是p使用一个这样的函数,那么对于两个函数它是p^2,我们可以通过这个技巧使它变得任意小。

答案 4 :(得分:0)

使用Rolling hashes

这将使运行时期望为O(n)。

这可能会重复pkacprzak的回答,除了它给出一个更容易记忆的名称等。

答案 5 :(得分:0)

后缀自动机也可以在O(N)中完成。

编码很容易,但很难理解。

以下是关于它的论文http://dl.acm.org/citation.cfm?doid=375360.375365

http://www.sciencedirect.com/science/article/pii/S0304397509002370