在线性时间内找到最长的双后缀

时间:2016-07-19 21:41:18

标签: string algorithm suffix-tree suffix

给定字符串s,找到时间复杂度O(|s|)中最长的双后缀。

示例:对于字符串banana,LDS为na。对于abaabaa,它是baa

显然我想过使用后缀树,但是我很难找到双后缀。

2 个答案:

答案 0 :(得分:0)

反转字符串并构建稀疏数组P[i][j],其中i0log(n)j0到{ {1}},n-1是字符串的长度。 n是指从位置P[i][j]和长度j开始的后缀的等级。因此,如果2^i,则索引P[i][j]=P[i][k]2^i的后缀的第一个j字符相等。

现在,您的问题减少为找到k的最长公共前缀(反向字符串的开头)和索引0的另一个后缀,例如i。 只需在LCP >= i时间内使用P数组,通过比较这两个后缀的前log(n)个字符并逐渐减少2^x来计算LCP。

总复杂度为x。 以下是可用的C ++源代码:https://ideone.com/aJCAYG

答案 1 :(得分:0)

我认为Gene的解决方案更容易实现,因为它不依赖于树状结构,而是依赖于阵列,它也可能更加硬件友好。

但是既然你提到了后缀树,让我们看一下基于后缀树的解决方案!我将假设您使用结束标记来标记您在树中插入的字符串的结尾。为了说明这一点,下面是为abaabaa示例构建的后缀树的表示:

$ - ##
b a a - $ - ## // Longest double suffix: P is the first dash, N the second
        b a a $ - ## // N' is the dash
a - $ - ##
    a - $ - ##
        b a a $ - ##
    b a a - $ - ##
            b a a $ - ##

N 是后缀树中的节点时,我们将表示 | N | N 表示的子字符串的长度。< / p>

如何在后缀树中表征“双后缀”?那么它是一个终端节点 N ,其父节点具有特定属性:让 P 成为双后缀的父节点,然后:

  • P 转换为后缀节点 N ,只包含字符串的结束标记($)。
  • 后缀成为节点 P 所代表的子字符串,并带有附加的结尾标记(在您的示例中为baa$)。如果我们从 P 向下走树,使用后缀,我们最终会进入另一个后缀节点 N'(沿着树向下走实际需要)
  • 节点 P 表示的子字符串是双后缀(在我们的例子中是baa)。
  • 我们有等于 | N'| = 2. | P | + 1 | N | = | P | + 1

鉴于此,您只需迭代后缀节点并测试此条件。如果以长度递减的顺序迭代后缀,则可能会贪婪:第一个匹配必然是最长的双后缀。

请注意,我们可以在检查长度| S | / 2的后缀并且只迭代奇数长度的后缀之后停止搜索(不要忘记我们在字符串中添加结束标记)

复杂性分析

构建后缀树是O(|S|) N'为后缀节点,N为后缀长度为(| N'| -1)/ 2 + 1 的后缀节点。假设树的正确构造:

  • 后缀可以按递增顺序存储在数组/向量中,因为树的创建会按照不断增加的长度顺序添加它们(至少使用Ukkonen的算法)。
  • 因此,访问长度为 k 的后缀为O(1)
  • 访问树节点所代表的子字符串为O(1),特别是,这适用于 P N 的父节点N'
  • 查明从 P N 的转换是否仅包含结束令牌($)是O(1)
  • 检查 | N'| = 2. | P | + 1 确实是O(1)

由于我们按照长度的递减顺序迭代后缀,我们必须关注N'后缀(加倍后缀,即示例中的baabaa$),所以我们只需要:< / p>

  • 获取 N 后缀节点,以便|N'| = 2.|N| - 1O(1)
  • 获取 P 后缀节点N的父级:O(1)
  • 检查从 P N 的转换是否仅包含结束标记$O(1)

证明: (我们忽略以下证明中的结束标记)

如果导致真正的评估,上面的3个步骤证明存在长度 2的后缀。| P | P 表示的子字符串开头,这也是一个后缀。由于此子字符串是后缀,因此长度 2。| P | 的后缀必须以它结尾,因此由两次出现的子字符串QED组成。

由于我们最多会为(|S|/2 + 1)/2个后缀执行此步骤,因此在最坏的情况下,识别步骤为O(|S|)

因此总体复杂性为O(|S|)