我正在尝试使用后缀树来比较字符串序列。我发现使用后缀树的最长公共子字符串问题的实现/理论。但是,我正在寻找的是相关问题的讨论-“所有常见子字符串”。具体来说,我有一个问题,我需要首先找到最长的公共子字符串,然后找到不包括已经找到的lcs索引的下一个最长的公共子字符串,依此类推直到最小长度。通过为两个序列构造一次通用后缀树(GST)可以解决此问题吗?我知道可以通过在每次查找和删除LCS的迭代之后重复构建GST来解决。但是,我想知道我是否错过了一个精巧的技巧,即在GST中仅构造一次。
我正在尝试使用后缀树来比较字符串序列。我发现使用后缀树的最长公共子字符串问题的实现/理论。但是,我正在寻找的是相关问题的讨论-“所有常见子字符串”。具体来说,我有一个问题,我需要首先找到最长的公共子字符串,然后找到不包括已经找到的lcs索引的下一个最长的公共子字符串,依此类推直到最小长度。通过为两个序列构造一次通用后缀树(GST)可以解决此问题吗?我知道可以通过在每次查找和删除LCS的迭代之后重复构建GST来解决。但是,我想知道我是否错过了一个精巧的技巧,即在GST中仅构造一次。
Answers:
是的,后缀树可用于查找所有常见的子字符串。我会说使用后缀数组来代替,但是如果您已经有一个后缀树,那么从后缀树构建后缀数组将需要DFS线性时间。因此,我的其余答案将假定我们正在使用后缀数组。
给定文本,的后缀数组是一个范围为到的整数数组,指定了字符串 $的后缀的字典顺序。
我们希望将后缀数组与最长的公共前缀耦合。如Kasai等人的论文所述,我们可以在线性时间内构造的阵列。后缀数组及其lcp数组以如下方式排列在一起:给定一个指向lcp数组的索引,例如,其中是索引号,则将是公共子字符串和的一个实例的开始将是第二个实例的起始索引。长度当然是lcp数组中的值。
我有一个可行的想法。我们从序列和的广义后缀树开始。每个内部节点的子树中都带有和后缀,它们对应于序列的某些公共子串。让我们称此类节点为非平凡的。如果相应的节点没有非平凡的子级,则公共子字符串最大。如果节点是非平凡的,则将非平凡节点的最大字符串深度存储在其子树中为。如果是根,则是和的最长公共子串的长度。
从一个序列中删除一个子串后更新树应该不会太难。我们首先删除与已删除后缀相对应的叶子,并在需要时更新其祖先。然后,我们开始处理已删除的子字符串之前的后缀。令为当前叶子的最低非平凡祖先。如果后缀的长度为(从删除开始,我们距离步骤)并且,则必须将后缀移动到树中的适当位置,并在需要时更新祖先。如果,我们就完成了,因为我们对具有琐碎根的子树不感兴趣。
只要LCS的长度足够大,整个算法就会反复查找和的最长公共子串,并从两个序列中删除其中一个出现的子串。
有一些技术性,但一般的想法应该可行。
从连接文本S $ T开始,其中$不在*或T中出现。从此文本构造后缀树/数组。现在很容易遍历此后缀数据结构以收集所有正确的最大重复数。通过检查左侧上下文,过滤掉非左侧最大重复。向左过滤可以使用Abouelhoda等人的Burrows-Wheeler表来实现,尽管我认为这不是必需的。重复仅在S或仅在T中发生应该在这一点上消除。然后,将尚未消除的重复放入优先级队列,优先级由长度定义。遍历后,将记录的重复项从优先级中删除,则可以执行最终过滤(用于子字符串包含)。但是,考虑到使用了最大短语,我怀疑很少需要这种过滤。
该算法是我自己的发明。我不会将其归类为非常聪明,但是它应该可以工作。
一个非常简单的解决方案:为第一个字符串创建后缀树,并用注释所有节点。然后插入第二个字符串所有后缀。注释您经过的节点或使用创建的节点。带有和注释的任何节点的路径标签是和的子字符串。(例如,参见这些讲义,可以进行快速的网络搜索。)