Алгоритм быстрого k несоответствия строк

10

Я ищу быстрый алгоритм сопоставления строк k-несоответствие. Учитывая строку шаблона P длины m и текстовую строку T длины n, мне нужен быстрый (линейное время) алгоритм, чтобы найти все позиции, где P соответствует подстроке T с не более чем k несоответствиями. Это отличается от проблемы k-отличий (редактировать расстояние). Несовпадение подразумевает, что подстрока и шаблон имеют разные буквы не более чем в k позициях. Я действительно требую только k = 1 (не более 1 несоответствия), поэтому быстрого алгоритма для конкретного случая k = 1 также будет достаточно. Размер алфавита составляет 26 (без учета регистра текста на английском языке), поэтому требования к пространству не должны расти слишком быстро с размером алфавита (например, алгоритм FAAST, я полагаю, занимает пространство по экспоненте в размере алфавита и т. Д. подходит только для последовательностей белков и генов).

Подход, основанный на динамическом программировании, будет иметь тенденцию быть O (mn) в худшем случае, который будет слишком медленным. Я считаю, что для этого есть модификации алгоритма Бойера-Мура, но я не могу достать такие бумаги. У меня нет подписки на доступ к академическим журналам или публикациям, поэтому любые ссылки должны быть в открытом доступе.

Я был бы очень признателен за любые указатели или ссылки на свободно доступные документы, или сам алгоритм для этой проблемы.

Paresh
источник
2
Если шаблон является фиксированным (но текст для соответствия варьируется), вы могли бы потенциально создать конечный автомат и пропустить текст через него. Существуют также алгоритмы, использующие суффиксные деревья (обычно это хорошо, если текст постоянен и шаблон меняется, но также применим, если оба варианта различаются), вы можете найти некоторые ссылки в Интернете. (Пока не добавляю ответ, так как я не очень уверен в алгоритмах на основе дерева суффиксов, если кто-то знает, пожалуйста, не стесняйтесь игнорировать этот комментарий).
Арьябхата
@Aryabhata Спасибо! И шаблон, и текст меняются. В этом контексте создание конечного автомата было бы слишком дорого, особенно если включить область для 1 несоответствия. Что касается суффиксных деревьев / массивов суффиксов, я никогда не использовал их и мало о них знаю, но у меня сложилось впечатление, что они медленны в построении и эффективны в основном для точного соответствия. Но я буду исследовать этот вариант дальше. Любые указатели в этом или любом другом направлении были бы наиболее полезны!
Пареш
1
Нет, суффиксные деревья можно использовать и для приблизительных совпадений. По крайней мере, вики утверждают так: en.wikipedia.org/wiki/Suffix_tree
Aryabhata

Ответы:

5

Для этой проблемы можно использовать суффиксные массивы . Они содержат начальные позиции каждого суффикса строки, отсортированного в лексикографическом порядке. Даже если они могут быть построены наивно в сложности , существуют методы, чтобы построить их в сложности Θ ( n ) . Смотрите, например, это и это . Давайте назовем этот суффиксный массив SA.O(nlogn)Θ(n)

После создания массива суффиксов нам необходимо создать массив Longest Common Prefix (LCP) для массива суффиксов. Массив LCP хранит длину самого длинного общего префикса между двумя последовательными префиксами в массиве суффиксов (лексикографические последовательные суффиксы). Таким образом, LCP [i] содержит длину самого длинного общего префикса между SA [i] и SA [i + 1]. Этот массив также может быть построен за линейное время: см. Здесь , здесь и здесь для некоторых хороших ссылок.

Теперь, чтобы вычислить длину самого длинного префикса, общего для любых двух суффиксов в дереве суффиксов (вместо последовательных суффиксов), нам нужно использовать некоторую структуру данных RMQ . В приведенных выше ссылках было показано (и это легко увидеть, если массив визуализировать как дерево суффиксов), что длина самого длинного общего префикса между двумя суффиксами, имеющими позиции и v ( u < v ) в массиве суффиксов , может быть получено как m i n u < = k < = v - 1 L C P [ k ]uvu<vminu<=k<=v1LCP[k], Хороший RMQ может предварительно обработать массив за O ( n ) или O ( n log n ) и ответить на запросы вида L C P [ u , v ] за O ( 1 ) . Смотрите здесь для краткого алгоритма RMQ, и здесь для хорошего учебника по RMQ и отношениям (и сокращениям) между LCA и RMQ. Это еще один хороший альтернативный подход.LCPO(n)O(nlogn)LCP[u,v]O(1)

С помощью этой информации мы создаем массив суффиксов и связанные с ними массивы (как описано выше) для объединения двух строк с разделителем между ними (например, T # P, где «#» не встречается ни в одной из строк). Затем мы можем выполнить k несоответствие строк, используя метод "кенгуру". Это и это объясняет метод кенгуру в контексте деревьев суффиксов, но может быть непосредственно применен и к массивам суффиксов. Для каждого индекса текста T найдите L C P суффикса T, начинающегося с i, и суффикса PiTLCPTiPначиная с 0. Это дает местоположение, после которого возникает первое несоответствие при сопоставлении с T [ i ] . Пусть эта длина будет l 0 . Пропустите несовпадающий символ в T и P и попробуйте сопоставить оставшиеся строки. То есть снова найдите L C P из T [ i + l 0 + 1 ] и P [ l 0 + 1 ] . Повторяйте это, пока не получите k несоответствий или не завершится строка. каждыйPT[i]l0TPLCPT[i+l0+1]P[l0+1]k является O ( 1 ) . Есть O ( K ) L С Р «S для каждого индекса я из T , давая это общая сложность O ( п к ) .LCPO(1)O(k) LCPiTO(nk)

O(nk+(n+m)log(n+m))O(nk+nlogn)m=O(n)O(nk)

Paresh
источник
Большой! Теперь у меня есть кое-что в моем списке TODO :-)
Aryabhata
Ссылка на siam.org во втором абзаце не работает, но ссылку на документ можно найти здесь epubs.siam.org/doi/pdf/10.1137/1.9781611972917.3
leecbaker
4

O(n+m)kO(nk+m)

Идея аналогична алгоритму хэширования Рабина-Карпа для точных совпадений подстрок.

m2km/2k2k2k

k

k

Я ожидаю (предостережение: я не пробовал сам), вероятно, это будет быстрее на практике и, возможно, будет легче кодировать / поддерживать, чем использование подхода на основе суффиксного дерева.

Арьябхата
источник
Просто нужно пояснение. Под "... отделяйте каждую строку длиной m на 2k блоков размером m / 2k каждый ...", вы подразумеваете, что разделяете каждую подстроку длины m в T (длиной n) на 2k блоков. И этот хэш может быть вычислен в O (n) методом скользящего хэша. Затем строка шаблона также будет разделена на 2k блоков, и будут сравниваться соответствующие хэши, что дает возможность не более чем k блоков не совпадать. Если это так, то мы могли бы потенциально отбросить все случаи, когда количество несоответствий превышает k. Я правильно понял?
Пареш
kΩ(nk)O(n)
Мне нравится этот подход! Тем не менее, этот подход в целом быстрый, но ухудшается до O (mnk), если количество совпадений велико (O (n) совпадений). Имея это в виду, я поддерживал два скользящих хэша, предполагая, что оба не могут иметь коллизию для одного и того же входа (я не делал этого математически, так как хотел видеть скорость). Таким образом, нам не нужно проверять совпадение char-by-char, если оба хэша совпадают. В целом это довольно быстро, но слишком много, если количество совпадений велико. С этим и с тем, как вы предложили, это было медленно для больших матчей.
Пареш
mm/2kmO(nkm)
mm/2k2kk+1k+cΩ(nm)mm/2k