使用后缀数组计算两个字符串的最长公共子字符串


15

在学习了如何以复杂度构建后缀数组之后,我对发现后缀数组的应用感兴趣。其中之一是在时间中找到两个字符串之间最长的公共子字符串。我在互联网上发现以下算法:O(N)O(N)

  1. 将两个字符串和合并为一个字符串ABAB
  2. 计算的后缀数组AB
  3. 计算(最长公共前缀)数组LCP
  4. 答案是最大值LCP[i]

我尝试实现它,但是由于没有说很多实现细节(即,在连接字符串时,是否应该在它们之间加上一个特殊字符()?),我的代码在许多测试用例上均失败了。有人可以详细说明一下该算法吗?AcB

提前致谢。

注意:我不保证该算法的正确性;我在博客上找到了它,但不确定它是否有效。如果您认为它不正确,请提出另一种算法。


3
在实施算法之前,请尝试了解其工作原理。这可以帮助回答一个问题,例如如何连接两个字符串。
Yuval Filmus 2013年

3
我怀疑这种算法的正确性。以和为例,我读它的方式将返回,这是错误的。b c d a b c dabcdabcdbCd一种bCd
Khaur

Answers:


20

您的算法不正确。我假设您知道如何计算字符串的后缀数组和LCP数组,即它们的有效实现。正如评论中指出的那样,您应该尝试了解每个组件是什么以及它为何起作用。

首先,是字符串的后缀数组()。后缀数组基本上是按字典顺序升序排列的字符串S的所有后缀。更具体地,值小号[ ]表示的后缀小号从位置开始小号[ ]排名在所有后缀的词典式排序小号小号一种小号小号一种[一世]小号小号一种[一世]一世小号

接下来是阵列。L C P [ i ]表示从S A [ i - 1 ]S A [ i ]开始的后缀之间的最长公共前缀的长度。也就是说,当按字典顺序排列时,它会跟踪S的两个连续后缀中最长的公共前缀的长度。LCPLCP[i]SA[i1]SA[i]S

例如,考虑字符串。按字典顺序排列的后缀为{ a a b b a b c a a b c a b a b c a b b a b c a b c a c a },因此S A = [ 7 1S=abbabca{a,abbabca,abca,babca,bbabca,bca,ca}对于1索引数组。的大号Ç P阵列将大号Ç P = [ - 1 2 0 1 1 0 ]SA=[7,1,4,3,2,5,6]LCPLCP=[,120110]

现在,给定两个字符串B,我们将它们连接为S = A B,其中AB都不存在的字符。选择这种字符的原因是,当计算两个后缀的LCP(例如a b d a b da b d)时,比较会在第一个字符串的末尾中断(因为它只会出现一次,两个不同的后缀永远不会在同一位置出现),也不会“溢出”到另一个字符串中。一种小号=一种一种一种bd一种bd一种bd

现在可以看出,您应该能够理解为什么只需要查看数组中的连续值(该参数基于矛盾以及S A中的后缀按字典顺序排列的事实)。继续检查L C P数组的最大值,以使要比较的两个后缀不属于同一原始字符串。如果它们不属于同一原始字符串(一个以A开头,另一个以B开头),则此类值中的最大值是最大的公共子字符串的长度。大号CP小号一种大号CP一种

例如,考虑B = b c。然后,S = a b c a b c b c。排序后缀为{ a b c b c a b c a b c b c b c b c b c b c a一种=一种bC一种bC=bC小号=一种bC一种bCbC小号{abc#bc,abcabc#bc,bc,bc#bc,bcabc#bc,c,c#bc,cabc#bc}
SA=[4,1,8,5,2,9,6,3,7]LCP=[,3,0,2,2,0,1,1,0]

现在,最大的值是,但它对于小号[ 1 ]小号[ 2 ],这两者开始字符串。因此,我们忽略了这一点。在另一方面,大号Ç P [ 4 ] = 2小号[ 3 ](对应于后缀b Ç)和小号[ 4 ]LCP[2]=3SA[1]SA[2]ALCP[4]=2SA[3]bcBSA[4](对应于后缀)。因此,这是两个字符串之间最长的公共子字符串。为了获取实际的子字符串,您可以从S A [ 3 ]S A [ 4 ](即b c)开始,选择长度2(最大可行L C P的值)子字符串。bcabc#bcA2 LCPSA[3]SA[4]bc


1
优秀的解释,但我认为,这个例子是一个有点不对劲,排序的后缀有:{#bc,abc#bc,abcabc#bc,bc,bc#bc,bcabc#bc,c,c#bc,cabc#bc}SA=[7,4,1,8,5,2,9,6,3]LCP=[−,0,3,0,2,2,0,1,1]
萨乌尔·马丁内斯Vidals

1

您在网上找到的算法并不完全正确。正如Paresh提到的那样,在他给出的示例中将失败。

但是,如果确保在检查LCP时仅检查不同字符串的子字符串的LCP。例如,如果要查找字符串A和B的LCS,则需要确保在检查LCP时,后缀数组的相邻条目都不来自同一字符串。

更多细节在这里


1
当您说“此答案”时,您是指您自己的答案还是其他答案?请仅使用答案框回答问题,而不对其他答案发表评论。获得足够的声誉后,您将可以对其他答案发表评论。
David Richerby

0

我认为,如果将不属于字符集的字符用作分隔符,并且构建后缀/前缀数组以排除包含分隔符的所有字符串,则您引用的算法确实应该可以工作,这可能是因为设计师。这基本上等效于为两个单独的字符串构建后缀/前缀数组。

如果您发布了该算法的链接,将对将来的参考很有帮助。请注意,维基百科采用伪代码和许多其他算法来实现这一目的。并且在线提供了大多数标准语言的实现。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.