как получить самую длинную повторяющуюся строку в подстроке из дерева суффиксов

Мне нужно найти самую длинную повторяющуюся строку в подстроке. Допустим, у меня есть строка "bannana"

Википедия говорит следующее:

В информатике проблема самой длинной повторяющейся подстроки — это задача поиска самой длинной подстроки строки, которая встречается не менее двух раз. На рисунке со строкой «ATCGATCGA$» самая длинная повторяющаяся подстрока — «ATCGA».

Итак, я предполагаю, что для строки "bannana" есть две подстроки одинаковой длины (если не так, поправьте меня): "an" и "na".

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

постройте дерево суффиксов, затем найдите самый высокий узел, по крайней мере, с двумя потомками.

Я нашел несколько реализаций суффиксных деревьев. Следующий код взят из здесь:

use strict;
use warnings;
use Data::Dumper;

sub classify {
    my ($f, $h) = (shift, {});
    for (@_) { push @{$h->{$f->($_)}}, $_ }
    return $h;
}
sub suffixes {
    my $str = shift;
    map { substr $str, $_ } 0 .. length($str) - 1;
}
sub suffix_tree {
    return +{} if @_ == 0;
    return +{ $_[0] => +{} } if @_ == 1;
    my $h = {};
    my $classif = classify sub { substr shift, 0, 1 }, @_;
    for my $key (sort keys %$classif) {
        my $subtree = suffix_tree(
            grep "$_", map { substr $_, 1 } @{$classif->{$key}}
        );
        my @subkeys = keys %$subtree;
        if (@subkeys == 1) {
            my $subkey = shift @subkeys;
            $h->{"$key$subkey"} = $subtree->{$subkey};
        } else { $h->{$key} = $subtree }
    }
    return $h;
}

print +Dumper suffix_tree suffixes 'bannana$';

для строки "bannana" он возвращает следующее дерево:

$VAR1 = {
          '$' => {},
          'n' => {
                   'a' => {
                            'na$' => {},
                            '$' => {}
                          },
                   'nana$' => {}
                 },
          'a' => {
                   '$' => {},
                   'n' => {
                            'a$' => {},
                            'nana$' => {}
                          }
                 },
          'bannana$' => {}
        };

Другая реализация находится в сети здесь, для строки "bannana" она возвращает следующее дерево:

 7: a
 5: ana
 2: annana
 1: bannana
 6: na
 4: nana
 3: nnana

     |(1:bannana)|leaf
tree:|
     |      |(4:nana)|leaf
     |(2:an)|
     |      |(7:a)|leaf
     |
     |     |(4:nana)|leaf
     |(3:n)|
     |     |(5:ana)|leaf
3 branching nodes

Вопросы:

  1. Как я могу получить из этих графиков строки "an" и "na"?
  2. Как видите, деревья разные, эквивалентны они или нет, если да, то почему разные, если нет, то какой алгоритм правильный?
  3. Если реализация perl неверна, есть ли работающая реализация для perl/python?
  4. Я читал об алгоритме Укконена, который также упоминается на странице со 2-м примером (я не понял, использует ли онлайн-версия этот алгоритм или нет), использует ли какой-либо из упомянутых примеров этот алгоритм? Если нет, то используется ли алгоритм медленнее или имеет недостатки по сравнению с Укконеном?

person Wakan Tanka    schedule 16.07.2015    source источник
comment
Не совсем понятно, как первой реализации удалось преобразовать bannana в banana.   -  person n. 1.8e9-where's-my-share m.    schedule 16.07.2015
comment
Первая реализация сомнительна: это bannana или banana? Второй выглядит неправильно: у него 5 листьев, но bannana имеет 7 букв, поэтому по определению у него должно быть 7 листьев.   -  person IVlad    schedule 16.07.2015
comment
Ваша нотация также сбивает с толку. Суффиксные деревья обычно обозначают ребра, а не узлы. Но вы, похоже, маркируете узлы, так что же представляют ваши метки?   -  person IVlad    schedule 16.07.2015
comment
извините, ребята, моя ошибка, я исправил bannana против banana. Это bannana @IVlad, если честно, понятия не имею. Моя первоначальная цель - найти самую длинную повторяющуюся подстроку, дерево суффиксов - всего лишь инструмент для этого, как именно они работают, я не знаю. Но что я понял, так это ответ на мою проблему.   -  person Wakan Tanka    schedule 16.07.2015
comment
Что вам нужно сделать, так это посмотреть на алгоритмы для вычисления самого длинного общего массива префиксов (обычно используется с массивами суффиксов) из дерева суффиксов.   -  person David Eisenstat    schedule 16.07.2015


Ответы (1)


1. Как я могу получить из этих графов строки "an" и "na"?

постройте дерево суффиксов, затем найдите самый высокий узел, по крайней мере, с двумя потомками.

string-node объединяет строки для каждого узла от корня до этого узла. highest node — это узел максимальной длины string-node.

Смотрите дерево в моем ответе на второй вопрос. (3:n) имеет 2 потомков и путь к узлу (2:a)->(3:n), конкатенация an. А также за (5:a) получить na.

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

Эти деревья разные. Перестроить второе дерево для строки "bannana$" (как в первом дереве):

 8: $
 7: a$
 5: ana$
 2: annana$
 1: bannana$
 6: na$
 4: nana$
 3: nnana$

     |(1:bannana$)|leaf
tree:|
     |     |     |(4:nana$)|leaf
     |     |(3:n)|
     |     |     |(7:a$)|leaf
     |(2:a)|
     |     |(8:$)|leaf
     |
     |     |(4:nana$)|leaf
     |(3:n)|
     |     |     |(6:na$)|leaf
     |     |(5:a)|
     |     |     |(8:$)|leaf
     |
     |(8:$)|leaf
5 branching nodes

3. Если реализация perl неверна, есть ли работающая реализация для perl/python?

Я не знаю Perl, но дерево построено правильно.

4. Я читал об алгоритме Укконена, который также упоминается на странице со 2-м примером (я не понял, использует ли онлайн-версия этот алгоритм или нет), использует ли какой-либо из упомянутых примеров этот алгоритм? Если нет, то используемый алгоритм медленнее или имеет недостатки по сравнению с Укконеном?

Ранее я говорил, что не знаю Perl, но эта строка в первом алгоритме означает, что он работает как минимум O(n^2) (n это длина строки):

map { substr $str, $_ } 0 .. length($str) - 1;

Алгоритм Укконена работает линейно за время O(n).

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

person aropan    schedule 02.08.2015