Autômato para correspondência de substring

7

Dado s como uma string sobre algum alfabeto, qual é o algoritmo mais conhecido para calcular um autômato determinístico de estado finito (DFA) determinístico que aceita qualquer string que contenha s?

Estou interessado principalmente na menor complexidade de tempo, portanto, se você me disser qual é a complexidade mais conhecida na notação O para criar um autômato para uma string que seria igualmente boa.

Ofek Ron
fonte

Respostas:

7

Hendrik Jan está correto sobre o algoritmo Knuth-Morris-Pratt (aviso, a wikipedia não o explica particularmente bem, um texto sobre algoritmos é provavelmente uma aposta melhor). A função de falha pode ser usada para extrair um DFA que pode executar a correspondência de string. Não é imediatamente óbvio que a tabela de falhas é um DFA, mas com muito pouco trabalho, a tabela de transição para o DFA pode ser construída. E seW é a sequência padrão que você deseja corresponder, o tempo para criar a tabela (e, portanto, construir o DFA) é O(|W|).

A ideia central é que a tabela de falhas T (Vou tentar usar os mesmos nomes da wikipedia, apenas para obter um exemplo pronto) mostra onde você pode estar na sequência se a correspondência atual não der certo, ou seja, se você não vir o próximo personagem que você está esperando, talvez o início da partida que você deseja já tenha sido visto, você está erroneamente mais à frente, então você só quer "voltar atrás" um pouco (note que não há retrocesso real no senso algorítmico usual).

Então, roubando descaradamente o exemplo da wikipedia, digamos que temos a tabela de falhas T:

i0123456W[i]ABCDABDT[i]1000012

Podemos construir o DFA da seguinte maneira; temos|W|+1estados, com uma espinha dorsal de transições que correspondem ao padrão inteiro. Para corresponder à tabela, chamaremos o estado inicialq1 e o estado final q|W| (então no exemplo q6) O backbone é então as transiçõesδ(qi1,W[i])=qi, então, no exemplo, vamos do estado inicial q1 declarar q0 em um A e de q2 para q3 com um D.

Agora, o truque é adicionar as transições "para trás" corretas, para que não retrocedamos ao longo da espinha dorsal quando entendemos algo errado. É aqui que nós a função de falha. Se estamos no estadoqi1 e não vemos W[i] como o próximo símbolo, podemos alternativamente fazer a transição para qT[i] se vemos W[T[i]]. Do exemplo, se estamos no estadoq5e não vemos um D, mas vemos uma C, podemos ir para q2. Todos os outros símbolos retornam ao estado inicial.

Reiterando; existem três tipos de transições, a transição de backbone, se tudo estiver indo bem, a transição dada pela tabela de falhas para que possamos recuperar um pouco e|Σ|2 outras transições que nos levam de volta ao estado inicial.

Os estados inicial e final são um pouco especiais, é claro, o estado inicial não pode voltar mais, então a transição de recuperação de falha aparece com as outras transições e, quando atingimos o estado final, não importa o que mais vemos , então também é um estado de afundamento.

Há uma ruga final, o caso em que o símbolo de falha é o mesmo que o esperado (por exemplo, W[1] e W[5]) nesse caso, podemos adiar a decisão até que sejam diferentes ou criar um NFA.

O exemplo resulta em um DFA parecido com (exceto erros):

DFA resultante do exemplo da tabela de falhas.

As transições tracejadas representam as transições "tudo o que resta".

Deve ser fácil ver que, dada a tabela, podemos construir o DFA em O(|W|)tempo (de uma só vez). A tabela também pode ser construída emO(|W|)time - o código fonte na wikipedia é suficiente. Se formos espertos, é claro que podemos pular a etapa intermediária da tabela e usar o algoritmo de criação de tabelas para criar o DFA imediatamente. Esse tempo de execução também é o melhor que podemos esperar, pois um DFA que corresponde apenas a essa sequência precisa ter pelo menos|W| transições (e |W|+1 estados), então precisamos ler a string inteira W, o que leva Ω(|W|) passos.

Luke Mathieson
fonte
5

Responda. Embora tenha visto uma resposta excluída, ainda acho que a construção é linear no comprimento da string (considerando o tamanho constante do alfabeto). Aho Corasick 1975, doi: 10.1145 / 360825.360855

Ou talvez KMP. Aho & Corasick consideram autômatos de correspondência de padrões para um conjunto finito de palavras, formando uma árvore com indicadores de retorno em caso de incompatibilidade = falhas. Isso está no estilo do algoritmo de Knuth-Morris-Pratt, que considera uma única string. Não consegui descobrir se o KMP realmente muda explicitamente dos autômatos de correspondência / falha para os autômatos determinísticos (com uma transição para todas as letras do alfabeto). Assim, basicamente a abordagem KMP é suficiente, juntamente com um argumento inteligente de que isso pode ser transformado em um DFA em tempo linear. A&C menciona explicitamente esse último passo. A tabela de falhas do KMP, no entanto, é um pouco mais simples que a construção do A&C (não é necessário uma árvore de seqüências de caracteres) e pode ser encontrada em toda a web.

Uma nota. Qualquer pessoa interessada em correspondência de padrões deve ler o artigo original do KMP, doi: 10.1137 / 0206024 , ou pelo menos as observações históricas da Seção 7. Citação: "Knuth ficou decepcionado ao saber que Morris já havia descoberto o algoritmo, sem conhecer o teorema de Cook [. .] ". O artigo da KMP foi publicado apenas em 1977, enquanto Morris já havia implementado sua versão em um editor de texto em 1969, enquanto Knuth e Pratt descobriram sua versão mais tarde como aplicação de uma prova de Cook.

Hendrik Jan
fonte