给定一个字符串和一个匹配的模式,找到匹配的效率有多为零或一个不匹配。
e.g)
S = abbbaaabbbabab
P = abab
Matches are abbb(index 0),aaab(index 4),abbb(index 6),abab(index 10)
我试图修改KMP算法,但我不确定这种方法。
请让我知道继续解决问题。
感谢。
答案 0 :(得分:5)
好的我找到了!我找到了最好的算法!
这可能听起来有点勇敢,但只要我要提出的算法同时具有运行时间O(m + n)
和内存消耗O(m + n)
并且条目数据本身具有相同的属性,算法就可以只在常数中进行优化。
我将在KMP和Rabin Karp算法之间使用混合来解决我的问题。 Rabin Karp使用rolling hashes来比较初始字符串的子字符串。它需要使用线性附加内存的线性及时预计算,但从那时起,两个字符串的子串之间的比较是常量O(1)
(如果正确处理冲突,则会摊销)。
我的解决方案找不到所有第一个字符串中与第二个字符串匹配且最多1个差异的匹配项。但是,可以修改算法,以便对于第一个字符串中的每个起始索引,如果存在这样的匹配,则至少会找到其中一个(这留给读者)。
让m
为第二个字符串的长度,n
- 第一个字符串的长度。我将把任务分成两部分:如果我的目标是找到最多只有一个差异的匹配,我想找到第一个字符串的子字符串:PREF
将成为单个字符串之前的子字符串差异和SUFF
差异之后的子串。我想要len(PREF) + len(SUFF) + 1 = m
,如果需要,PREF
或SUFF
会被人为地缩短(当字符串匹配时没有差异)。
我将基于一个非常重要的观察来建立我的解决方案:假设第一个字符串的子字符串从索引i
开始,长度为m
,与第二个字符串匹配,最多只有一个差异。然后,如果我们尽可能长时间PREF
,仍然会有SUFF
的解决方案。这很明显:我只是尽可能地推动差异。
现在遵循算法本身。从通常的KMP开始。每次前缀的扩展失败并且要遵循失败链接时,首先检查是否跳过下一个字母,剩余的后缀是否与第二个字符串的剩余部分匹配。如果是这样,则找到与最多一个字符差异的所寻求的匹配。如果没有 - 我们继续使用普通的KMP,每次跟踪失败链接时都要检查Rabin Karp。
让我用一个例子进一步澄清Rabin Karp检查。假设我们处于KMP的某个步骤,我们发现first.substring[i, i + k - 1]
匹配第二个字符串的第一个k
个字母。还假设字母first[i + k]
与second[k]
不同。然后使用Rabin Karp检查first.substring[i + k + 1, i + m - 1]
是否与second.substring[k + 1, m - 1]
完全匹配。这正是您尽可能扩展了索引i
的起始前缀的情况,现在您可以尝试最多只有一个差异。
只有在遵循失败链接时才会使用Rabin Karp,这会使前缀的起始索引至少移动一个,这意味着最多使用O(n)
Rabin Karp调用,每个调用都具有复杂性{ {1}}总线性复杂度。
答案 1 :(得分:2)
这称为approximate string matching问题。在您的特定情况下,您希望最大编辑距离为1.
bitap algorithm是一种解决问题的快捷方式。
答案 2 :(得分:2)
要查找包含一个不匹配的所有子匹配,您需要2个z函数(一个用于原始P,另一个用于反向P)。 在原始和反向字符串S的最长前缀子匹配的buld数组之后。 稍后你需要反转第二个数组。 最后一切都很简单:通过第一个数组并检查最长前缀的长度是否等于P的长度。如果是,那么它是一个没有错误的匹配。 如果它更短,则检查位置的第二个数组(i +长度(P) - 1)。如果总和 两个值等于长度(P) - 1,那么它是一个有一个错误的子匹配。
复杂性是O(len(P)+ len(S))
答案 3 :(得分:1)
Gonzalo Navarro在他的A guided tour to approximate string matching中给出了各种算法的全面概述以及它们如何相互比较。第80,81和82页显示了复杂性结果,包括最差和平均情况,以及各种算法的空间要求。
(在那里使用的符号中,n表示您搜索的文本的长度,m表示模式的长度,σ表示字母的大小,k表示最大编辑距离,这是在你的情况下1。)