使用后缀树/数组的最长非重叠重复子串(仅限算法)

时间:2012-09-30 03:57:48

标签: substring suffix-tree suffix-array

我需要在String中找到最长的非重叠重复子字符串。我有可用字符串的后缀树和后缀数组。

当允许重叠时,答案是微不足道的(后缀树中最深的父节点)。

例如对于String =“acaca”

如果允许重叠,则回答为“aca”,但如果不允许重叠,则回答为“ac”或“ca”。

我只需要算法或高级别的想法。

P.S。:我试过,但我在网上找不到明确的答案。

8 个答案:

答案 0 :(得分:4)

生成后缀数组并在O(nlogn)中排序.ps:有更有效的算法,如DC3和Ukkonen算法。 例如:

字符串:ababc 后缀数组: 子串的start-index |子
0 - ababc
2 - abc
1 - babc
3 - bc
4 - c


比较每两个连续的子串并获得具有以下约束的公共前缀:
i1 是子字符串索引" ababc ": 0
i2 是子字符串的索引" abc ": 2
通用前缀是" ab " ,公共前缀的长度为 len


abs(i1-i2)> len //避免重叠


使用解决方案通过后缀数组,您将获得" ababc"的结果,这是" ab ";

整个解决方案将运行O(nlogn)

然而,会有一个特例:" aaaaa"这个解决方案无法彻底解决 欢迎讨论并提出O(nlogn)但不是O(n ^ 2)

的解决方案

答案 1 :(得分:2)

不幸的是,Perkins提出的解决方案不起作用。我们不能强迫我们通过解决方案来找到长重复的非重叠子串。考虑香蕉的后缀树:http://en.wikipedia.org/wiki/Suffix_tree。将首先考虑以“A”作为其父节点的“NA”分支节点,因为它具有最大长度并且是分支节点。但它构造的字符串“ANA”是重叠的,因此它将被拒绝。现在,要考虑的下一个节点是“NA”,它将显示非重叠长度2,但子串“AN”将永远不会被考虑,因为它已经在已经考虑过的ANA字符串中表示。因此,如果您正在搜索所有重复的非重叠子串,或者当您想要第一个按字母顺序排列时,您就会失败。

显然有一种涉及后缀树的方法可行,但更简单的方法在这里列出:http://rubyquiz.com/quiz153.html

希望这有帮助!

答案 2 :(得分:1)

最简单的解决方案是暴力攻击。你有一个算法来找到最长的重叠允许字符串,使用它,检查答案是否有重叠,如果是,找到第二个最长的,检查它是否有重叠,等等。这会将它减少到现有的搜索算法,然后是正则表达式计数操作。

答案 3 :(得分:1)

通过构造后缀树,共享前缀P的所有后缀将成为树中共同祖先的后代。通过存储该子树后缀的最大和最小索引,我们可以保证长度为min(depth,max-min)的重复非重叠子串,其中max-min是它们之间的距离,depth是长度他们共同的前缀。所需的值是具有最大此值的节点。

答案 4 :(得分:0)

这可以使用“计算最长的先前非重叠因子”中给出的结果来解决(参见http://dx.doi.org/10.1016/j.ipl.2010.12.005

答案 5 :(得分:0)

完整代码:

#include <bits/stdc++.h>
using namespace std;
int cplen(string a,string b){
    int i,to=min(a.length(),b.length());
    int ret=0;
    for(i=0;i<to;i++){
        if(a[i]==b[i])ret++;
        else {
            return ret;}
        }
    return ret;
    }
int main(){
    {
        int len,i;
        string str;
        cin>>str;
        len=str.length();
        vector<pair<string,int> >vv;
        map<char,int>hatbc;
        string pp="";
        for(i=len-1;i>=0;i--){
            hatbc[str[i]]++;
            pp=str.substr(i,len-i);
            vv.push_back(make_pair(pp,i));
            }
        if(len==1 || (int)hatbc.size()==len){
            printf("0\n");
            continue;
            }
        if(hatbc.size()==1){
            printf("%d\n",len/2);
            continue;
            }
        char prev=str[0];
        int foo=1,koo=0;
        for(i=1;i<len;){
            while(str[i]==prev && i<len){i++;foo++;}
            prev=str[i];
            i+=1;
            if(koo<foo)koo=foo;
            foo=1;
            }

        sort(vv.begin(),vv.end());
        int ans=0;
        ans=koo/2;
        for(i=1;i<(int)vv.size();i++){
            int j=i-1;
            int a=vv[j].second,b=vv[i].second;
            string sa=vv[j].first,sb=vv[i].first;
            int cpl;
            cpl=cplen(sa,sb);
            if(abs(a-b)>=cpl)
                ans=max(ans,cpl);
            }
        printf("%d\n",ans);
        }
    return 0;
    }

复杂性:O(n * log(n))(由于排序)

答案 6 :(得分:0)

我们使用longest common prefix (LCP) array和后缀数组在O(n log n)时间内解决这个问题。

LCP数组为我们提供了后缀数组中两个连续后缀之间最长的公共前缀。

在构造LCP数组和后缀数组之后,我们可以二进制搜索答案的长度。

假设字符串是&#34; acaca $&#34;。后缀数组在代码片段中以表格形式给出。

&#13;
&#13;
<table border="1">
<tr><th>Suffix Array index</th><th>LCP</th><th>Suffix (implicit)</th></tr>
<tr><td>5</td><td>-1</td><td>$</td></tr>
<tr><td>4</td><td>0</td><td>a$</td></tr>
<tr><td>2</td><td>1</td><td>aca$</td></tr>
<tr><td>0</td><td>3</td><td>acaca$</td></tr>
<tr><td>3</td><td>0</td><td>ca$</td></tr>
<tr><td>1</td><td>2</td><td>caca$</td></tr>
</table>
&#13;
&#13;
&#13;

让二进制搜索答案的长度。

如果我们有一个答案,那么让两个子字符串对应两个后缀。

无法保证这些后缀在后缀数组中是连续的。但是,如果我们知道子字符串的长度,我们可以看到子字符串的两个后缀之间的LCP表中的每个条目至少是该数字。此外,两个索引之间的差异必须至少为该数字。

猜测子串的长度是一定量,我们可以考虑至少连续运行的LCP数组条目。在每次连续运行中,找到具有最大和最小索引的后缀。

我们怎么知道我们的猜测是下限?

如果某些[连续运行的LCP数组条目中的最大和最小索引之间的距离至少是我们的猜测]至少是我们的猜测,那么,我们的猜测是可达到的下限。

我们怎么知道我们的猜测太大了?

如果所有[连续运行的LCP数组条目中的最大和最小索引之间的距离至少是我们的猜测]小于我们的猜测,那么,我们的猜测太大了。

我们如何根据答案的长度找到答案?

对于每个[连续运行至少是答案的LCP数组条目],找到最低和最高索引。如果它们至少在答案上存在差异,那么我们就会返回最长的非重叠重复子串从这些索引开始。

在你的例子中,&#34; acaca $&#34;,我们可以发现答案的长度是2。

所有运行都是: &#34; aca $&#34;,&#34; acaca $&#34;,并且较低和较高索引之间的距离为2,导致重复的子串&#34; ac&#34;。

&#34; caca $&#34;,&#34; ca $&#34;,并且较低和较高指数之间的距离为2,导致重复的子串&#34; ca&#34;

答案 7 :(得分:0)

由于我很难找到一个有效的算法描述,以便使用后缀树来获取最长的不重叠的重复子字符串,因此,我想分享从各种来源收集的版本。

算法

  1. 构造输入字符串 S 的后缀树(以 S 中不会出现的特殊字符终止)。每个叶节点都对应于 S 的后缀 S i ,并为其分配了 i的相应起始位置 i S 中的> S i
  2. 为树中的每个节点分配一对(i min ,i max ,该对指示该子树的最小和最大后缀索引。
    1. 对于每片叶子 i min = i max = i
    2. 对于每个内部节点, i min 是最小值, i max 是所有后代的最大索引节点的索引。
  3. 对于所有内部节点 v ,让 P v 表示通过连接路径上所有边缘标签(前缀)获得的字符串。根到 v 。收集所有满足 i min + length( P v )≤ i max 对应的 i min i v 处> max 。
  4. 这些 P v 中最长的是 S 的最长的不重叠子串,该子串至少出现两次。

说明

如果 S 的子字符串在 S 中至少出现两次,则它是两个后缀 S <的公共前缀 P 。 sub> i S j ,其中 i j 分别表示 S 中的起始位置。因此,在 S 的后缀树中存在一个内部节点 v ,该内部节点具有两个后代叶子,分别对应于 i j < / em>,这样从根到 v 的路径的所有边缘标签的串联等于 P

最深的此类节点 v (根据其相应前缀的长度)标记了 S 中最长的,可能重叠重复的子字符串。为了确保不考虑重叠的子字符串,我们必须确保 P 不大于 i j 之间的距离。

因此,我们计算每个节点的最小和最大索引 i min i max 对应于 S 的最左后缀和最右后缀的位置,它们具有相同的前缀。从节点的后代值可以轻松获得其最小和最大索引。 (如果我们要寻找出现至少 k 次的最长子串,则索引计算会更加复杂,因为这时必须考虑所有后代索引的距离,而不仅仅是两个通过仅考虑满足 i min + length( P )≤的前缀 P i max ,我们确保从 S i 开始的 P 足够短,不会与后缀 S j

附加说明

  • 可以在Θ( n )时空中构造长度为 n 的字符串的后缀树。对该算法的修改不会使渐近行为恶化,因此总运行时间仍在Θ( n )中。
  • 此算法无法找到所有可能的解决方案。如果有几个不重叠的最长子字符串,则只能找到从最大位置开始的子字符串。
  • 应该有可能修改算法,以计算最长的非重叠子字符串的重复次数,或者仅找到重复次数至少为 k 的解决方案。为此,不仅必须考虑最小和最大指令,而且要考虑节点上所有子树的索引。然后,必须对每个相邻的索引对保持上述范围条件。