在一个巨大的字符串中找到长重复的子串

时间:2008-12-29 21:56:56

标签: performance algorithm string search

我天真地想象我可以构建一个后缀trie,其中我保持每个节点的访问计数,然后计数大于1的最深节点是我正在寻找的结果集。

我有一个非常长的字符串(数百兆字节)。我有大约1 GB的RAM。

这就是为什么用计数数据构建后缀trie在空间方面效率太低而无法为我工作。引用Wikipedia's Suffix tree

  

存储字符串的后缀树通常比存储字符串本身需要更多的空间。

     

每个边缘和节点中的大量信息使得后缀树非常昂贵,在良好的实现中消耗了源文本的内存大小的大约十到二十倍。后缀阵列将此要求降低到四分之一,研究人员继续寻找更小的索引结构。

那是维基百科对树的评论,而不是特里。

如何在如此大量的数据中以及在合理的时间内(例如在现代台式机上不到一小时)找到长重复序列?

(一些维基百科链接,以避免人们将其作为'答案'发布:Algorithms on strings,尤其是Longest repeated substring problem ;-))

9 个答案:

答案 0 :(得分:6)

执行此操作的有效方法是创建子字符串的索引并对其进行排序。这是一个O(n lg n)操作。

BWT压缩执行此步骤,因此它是一个很好理解的问题,并且存在基数和suffix(声明O(n))排序实现等,以使其尽可能高效。对于大文本来说,它仍然需要很长时间,也许需要几秒钟。

如果你想使用实用程序代码,C ++ std::stable_sort()对于自然语言比std::sort()执行更多(并且比C qsort()快得多,但对于不同的原因)。

然后访问每个项目以查看其公共子字符串与其邻居的长度为O(n)。

答案 1 :(得分:3)

您可以查看基于磁盘的后缀树。我通过Google找到了这个Suffix tree implementation library,还有一些文章可以帮助你自己实现它。

答案 2 :(得分:2)

你可以用分而治之来解决这个问题。我认为这应该与使用trie的算法复杂度相同,但实现方式可能效率较低

void LongSubstrings(string data, string prefix, IEnumerable<int> positions)
{
    Dictionary<char, DiskBackedBuffer> buffers = new Dictionary<char, DiskBackedBuffer>();
    foreach (int position in positions)
    {
        char nextChar = data[position];
        buffers[nextChar].Add(position+1);
    }

    foreach (char c in buffers.Keys)
    {
        if (buffers[c].Count > 1)
            LongSubstrings(data, prefix + c, buffers[c]);
        else if (buffers[c].Count == 1)
            Console.WriteLine("Unique sequence: {0}", prefix + c);
    }
}

void LongSubstrings(string data)
{
    LongSubstrings(data, "", Enumerable.Range(0, data.Length));
}

在此之后,您需要创建一个实现DiskBackedBuffer的类,使其成为一个数字列表,当缓冲区达到一定大小时,它将使用临时文件将其自身写入磁盘,并从读取时的磁盘。

答案 3 :(得分:2)

回答我自己的问题:

鉴于长距离比赛也是一场短暂的比赛,你可以通过首先找到较短的比赛,然后看看你是否可以“增长”这些比赛来换取RAM的多次传球。

对此的文字方法是在数据中构建一些固定长度的所有序列的trie(在每个节点中具有计数)。然后,您将剔除与您的条件不匹配的所有节点(例如,最长匹配)。然后进行后续的数据传递,将trie更深入地构建,但不是更广泛。重复,直到找到最长的重复序列。

一位好朋友建议使用哈希。通过从每个字符开始散列固定长度字符序列,您现在遇到了查找重复哈希值的问题(并验证重复,因为哈希是有损的)。如果为数组分配一个数据长度来保存哈希值,你可以做一些有趣的事情,例如:要查看匹配是否比数据的固定长度传递长,您可以只比较哈希序列而不是重新生成它们。等

答案 4 :(得分:2)

这样的简单程序怎么样:

S = "ABAABBCCAAABBCCM"

def findRepeat(S):
    n = len(S)
    #find the maxim lenth of repeated string first 
    msn = int(floor(n/2))
    #start with maximum length 
    for i in range(msn,1,-1):
        substr = findFixedRepeat(S, i)
        if substr:
            return substr
    print 'No repeated string'
    return 0

def findFixedRepeat(str, n):
    l = len(str)
    i = 0
    while  ((i + n -1) < l):
        ss = S[i:i+n]
        bb = S[i+n:]
        try:
            ff = bb.index(ss)
        except:
            ff = -1

        if ff >= 0:
            return ss;
        i = i+1
    return 0
print findRepeat(S)

答案 5 :(得分:1)

这个文字是否有单词分词?然后我怀疑你想要一个关键词在上下文中的变体:为一行中的n个单词复制每行n次,打破每个单词的每一行;排序整个事物的alpha;寻找重复。

如果它是一个长的鸣笛字符串,比如说生物信息学DNA序列,那么你想在磁盘上构建像你的trie;为每个字符构建一个记录,并为下一个节点提供磁盘偏移量。我将看一下Knuth第3卷第5.4节“外部排序”。

答案 6 :(得分:0)

您可以通过构建suffix array来解决您的问题吗?否则,您可能需要使用其他答案中提到的其中一个基于磁盘的后缀树。

答案 7 :(得分:0)

对我来说只是一个迟来的想法......

取决于您的操作系统/环境。 (例如64位指针&amp; mmap()可用。)

您可以通过mmap()在磁盘上创建一个非常大的后缀树,然后将该树中缓存的最常访问的子集保存在内存中。

答案 8 :(得分:-1)

对于更多RAM,最简单的方法可能是plunk down the $100。否则,您可能必须查看磁盘支持的结构以保存后缀树。