这个算法是线性的吗?

时间:2011-12-19 15:13:45

标签: c algorithm big-o

受到这两个问题的启发:String manipulation: calculate the "similarity of a string with its suffixes"Program execution varies as the I/P size increases beyond 5 in C,我提出了以下算法。

问题将是

  1. 这是正确的,还是我在推理中犯了错误?
  2. 算法的最坏情况复杂性是什么?
  3. 首先是一些背景。对于两个字符串,将它们的相似性定义为两者的最长公共前缀的长度。字符串 s 的总自相似性是 s 与其所有后缀的相似之处的总和。因此,例如, abacab 的总自相似性为6 + 0 + 1 + 0 + 2 + 0 = 9且 a 的总自我相似性重复{{ 1}}次是n

    算法说明:该算法基于Knuth-Morris-Pratt字符串搜索算法,因为字符串前缀的 border 起着核心作用。

    总结一下:字符串 s border s 的正确子字符串 b ,它同时存在前缀和后缀 s

    备注:如果 b c s 的边界,其中 b 短于 c < / em>,然后 b 也是 c 的边框,相反, c 的每个边界也是 s的边框

    s 为长度 n 的字符串, p s 的前缀,长度为 I 的。如果n*(n+1)/2或{{em} {1}},否则它是可扩展的( s 的长度i == n前缀是 s 的长度s[i] != s[k]前缀的边框。

    现在,如果 s 的最长公共前缀和以k+1开头的后缀长度 k ,则长度 k s 的前缀是 s 的长度 i + k 前缀的不可扩展边框。它是一个边框,因为它是 s i+1的公共前缀,如果它是可扩展的,它将不是最长的公共前缀。

    相反, s 的长度 i 前缀的每个不可扩展的边界(长度 k )是<的最长公共前缀< em> s 以及以s[i], i > 0开头的后缀。

    因此,我们可以通过对 s i 前缀的所有不可扩展边界的长度求和来计算 s 的总自相似性s[i .. n-1]。要做到这一点

    1. 通过标准KMP预处理步骤计算前缀最宽边框的宽度。
    2. 计算前缀最宽的不可扩展边框的宽度。
    3. 对于每个 i s[i-k],如果1 <= i <= n具有非空的不可扩展边框,请让 b 成为最宽的,添加 b 的宽度以及 b 的所有非空边框 c ,如果它是的不可扩展边框p ,加上它的长度。
    4. 添加 s 的长度 n ,因为上述内容未涵盖。
    5. 代码(C):

      1 <= i <= n

      那么,这是正确的吗?如果没有,我会感到很惊讶,但我以前错了。

      算法的最坏情况复杂度是什么?

      我认为它是O(n),但我还没有找到证据证明前缀在其最宽的不可扩展边界中可以包含的可扩展边界的数量是有界的(或者更确切地说,这样的总数)发生的是O(n))。

      我对尖锐的界限最感兴趣,但如果你能证明它是例如小p = s[0 .. i-1]的O(n * log n)或O(n ^(1 + x)),已经很好了。 (这显然是最坏的二次方,所以“它是O(n ^ 2)”的答案只有在伴有二次或近二次行为的例子时才有意思。)

2 个答案:

答案 0 :(得分:16)

这看起来非常简洁,但遗憾的是我认为最坏的情况是O(n ^ 2)。

以下是我对一个反例的尝试。 (我不是数学家所以请原谅我使用Python代替方程来表达我的想法!)

考虑带有4K + 1符号的字符串

s = 'a'*K+'X'+'a'*3*K

这将有

borders[1:] = range(K)*2+[K]*(2*K+1)

ne_borders[1:] = [-1]*(K-1)+[K-1]+[-1]*K+[K]*(2*K+1)

请注意:

1)ne_borders [i]将等于K的(2K + 1)i值。

2)对于0&lt; = j&lt; = K,border [j] = j-1

3)算法中的最后一个循环将进入内部循环,j == K表示2K + 1个值

4)内循环将迭代K次以将j减少到0

5)这导致算法需要多于N * N / 8次操作才能完成长度为N的最坏情况字符串。

例如,对于K = 4,它绕内圈循环39次

s = 'aaaaXaaaaaaaaaaaa'
borders[1:] = [0, 1, 2, 3, 0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4]
ne_borders[1:] = [-1, -1, -1, 3, -1, -1, -1, -1, 4, 4, 4, 4, 4, 4, 4, 4, 4]

对于K = 2,248,它绕内环10,111,503次!

也许有办法解决这种情况的算法?

答案 1 :(得分:8)

你可能想看看Z算法,这可以证明是线性的:

s是长度为N的C字符串

Z[0] = N;
int a = 0, b = 0;
for (int i = 1; i < N; ++i)
{
  int k = i < b ? min(b - i, Z[i - a]) : 0;
  while (i + k < N && s[i + k] == s[k]) ++k;
    Z[i] = k;
  if (i + k > b) { a = i; b = i + k; }
}

现在相似性只是Z条目的总和。