Performance 找到到任何子串的最小汉明距离的最快方法?

Performance 找到到任何子串的最小汉明距离的最快方法?,performance,algorithm,string,Performance,Algorithm,String,给定一个长字符串L和一个短字符串S(约束条件是L。长度必须>=S。长度),我想找到S和L的任何子字符串之间的最小汉明距离,长度等于S。让我们为这个minHamming()调用函数。比如说, minHamming(ABCDEFGHIJ,CDEFGG)==1 minHamming(ABCDEFGHIJ,BCDGHI)==3 显然,这样做(枚举L的每个子字符串)需要O(S.length*L.length)时间。在次线性时间里有什么聪明的方法可以做到这一点吗?我用几个不同的S字符串搜索相同的L,因此对L

给定一个长字符串
L
和一个短字符串
S
(约束条件是
L
。长度必须>=
S
。长度),我想找到
S
L
的任何子字符串之间的最小汉明距离,长度等于
S
。让我们为这个
minHamming()
调用函数。比如说,

minHamming(ABCDEFGHIJ,CDEFGG)==1

minHamming(ABCDEFGHIJ,BCDGHI)==3

显然,这样做(枚举L的每个子字符串)需要O(
S
.length*
L
.length)时间。在次线性时间里有什么聪明的方法可以做到这一点吗?我用几个不同的
S
字符串搜索相同的
L
,因此对
L
进行一次复杂的预处理是可以接受的


编辑:修改后的Boyer Moore是个好主意,除了我的字母表只有4个字母(DNA)。

就big-O而言,你被卡住了。。在基本级别上,您需要测试目标中的每个字母是否与子字符串中的每个合格字母匹配

幸运的是,这很容易并行化

您可以应用的一个优化是为当前位置保留不匹配的运行计数。如果它大于目前为止的最低汉明距离,那么显然你可以跳到下一种可能性。

修改过的Boyer-Moore 我刚刚挖掘了一些我曾经使用过的旧Python实现,并修改了匹配循环(其中文本与模式进行比较)。不要在发现两个字符串之间的第一个不匹配时立即中断,只需计算不匹配的数量,但是记住第一个不匹配

current_dist = 0
while pattern_pos >= 0:
    if pattern[pattern_pos] != text[text_pos]:
        if first_mismatch == -1:
            first_mismatch = pattern_pos
            tp = text_pos
        current_dist += 1
        if current_dist == smallest_dist:
           break

     pattern_pos -= 1
     text_pos -= 1

 smallest_dist = min(current_dist, smallest_dist)
 # if the distance is 0, we've had a match and can quit
 if current_dist == 0:
     return 0
 else: # shift
     pattern_pos = first_mismatch
     text_pos = tp
     ...
如果此时字符串未完全匹配,请通过恢复值返回到第一次不匹配的点。这样可以确保实际找到最小的距离

整个实现相当长(~150LOC),但我可以根据请求发布它。核心思想如上所述,其他一切都是标准的Boyer Moore

文本预处理 另一种加速的方法是对文本进行预处理,以便在字符位置上建立索引。您只希望在两个字符串之间至少发生一次匹配的位置开始比较,否则汉明距离很小

import sys
from collections import defaultdict
import bisect

def char_positions(t):
    pos = defaultdict(list)
    for idx, c in enumerate(t):
        pos[c].append(idx)
    return dict(pos)
此方法仅创建一个字典,将文本中的每个字符映射到其出现的排序列表

比较循环或多或少与naive
O(mn)
方法相同,除了我们没有将每次开始比较的位置增加1,而是基于角色位置:

def min_hamming(text, pattern):
    best = len(pattern)
    pos = char_positions(text)

    i = find_next_pos(pattern, pos, 0)

    while i < len(text) - len(pattern):
        dist = 0
        for c in range(len(pattern)):
            if text[i+c] != pattern[c]:
                dist += 1
                if dist == best:
                    break
            c += 1
        else:
            if dist == 0:
                return 0
        best = min(dist, best)
        i = find_next_pos(pattern, pos, i + 1)

    return best
对于每个新位置,我们都会找到L中出现S中字符的最低索引。如果不再有此类索引,算法将终止

find_next_pos
肯定很复杂,可以尝试通过仅使用模式的前几个字符来改进它,或者使用一个集合来确保模式中的字符不会被检查两次

讨论 哪种方法更快很大程度上取决于您的数据集。你的字母表越多样化,跳跃就越大。如果你有一个很长的L,那么第二个预处理方法可能会更快。对于非常非常短的字符串(如您的问题中),天真的方法肯定是最快的

脱氧核糖核酸
如果你有一个非常小的字母表,你可以尝试获取字符双字符表(或更大)而不是单字符表的字符位置。

也许令人惊讶的是,这个精确的问题可以通过使用快速傅立叶变换(FFT)在O(| a | nlog n)时间内解决,其中n是较大序列的长度
L
| A |
是字母表的大小

以下是唐纳德·本森(Donald Benson)的一篇论文的PDF格式,介绍了它的工作原理:

  • (唐纳德·本森,核酸研究1990年第18卷,第3001-3006页)
摘要:将每个字符串
S
L
转换为几个指示向量(每个字符一个,如果是DNA,则为4个),然后转换相应的向量以确定每个可能对齐的匹配计数。诀窍在于,通常需要O(n^2)时间的“时间”域中的卷积可以通过使用“频率”域中的乘法来实现,而“频率”域只需要O(n)时间,加上在域之间转换和返回所需的时间。使用FFT,每次转换只需要O(nlog n)时间,因此总体时间复杂度为O(| A | nlog n)。为了获得最大的速度,使用有限域FFT,它只需要整数运算

注意:对于任意的
S
L
,该算法显然比直接的O(mn)算法有巨大的性能优势,因为
|S |
|L |
变大,但是如果
S
通常比
log | L |
短(例如,在用小序列查询大数据库时),显然,这种方法没有加速


更新21/7/2009:更新后,时间复杂度也与字母表的大小成线性关系,因为字母表中的每个字符都必须使用一对单独的指示符向量。

如果S短于L,您的约束应该是(L.length>=S.length)吗?有多少个是“几个不同的”?相对而言/绝对而言,| L |和| S |有多大~~~~哦,对了,很抱歉所有的代码示例都是用Python编写的,但这正是我最熟悉的语言。请随时要求澄清!就个人而言,我认为人们应该总是用他们最熟悉的语言发布代码。如果没有别的,它会教别人一些关于这种语言的知识。+1表示干净的代码示例,而我会再次+1(如果允许的话)表示bigram或bigge
def find_next_pos(pattern, pos, i):
    smallest = sys.maxint
    for idx, c in enumerate(pattern):
        if c in pos:
            x = bisect.bisect_left(pos[c], i + idx)
            if x < len(pos[c]):
                smallest = min(smallest, pos[c][x] - idx)
    return smallest