# Máquina de estados finitos não determinística

Na teoria da computação, uma máquina de estados finita não-determinística ou um autômato finito não-determinístico (AFND) é uma máquina de estados finita onde para cada par de estado e símbolo de entrada pode haver vários próximos estados possíveis. Isso o distingue do autômato finito determinístico (AFD), onde o próximo estado possível é univocamente determinado. Embora AFD e AFND possuam definições distintas, pode ser mostrado na teoria formal que eles são equivalentes e, deste modo, para qualquer AFND dado, pode-se construir um AFD equivalente e vice-versa: essa é a construção do conjunto das partes. Ambos os tipos de autômatos reconhecem apenas linguagens regulares. Máquinas de estados finitas não-determinísticas são às vezes estudadas com o nome de subshifts de tipo finito.
Máquinas de estados finitas não-determinísticas são generalizadas pelo autômato probabilístico, o qual atribui uma probabilidade para cada transição de estado, e por várias outras maneiras, tais como autômatos com pilha e AFNs com transições ε.
Autômatos finitos não-determinísticos foram introduzidos em 1959 por Michael O. Rabin e Dana Scott, que também mostraram sua equivalência com autômatos finitos determinísticos.

## Explicação intuitiva

Um AFND, similarmente a um AFD, lê uma cadeia de símbolos de entrada. Para cada símbolo da entrada há uma transição para um novo estado, até que todos os símbolos de entrada sejam lidos.
Ao contrário de um AFD, há um não-determinismo onde, para cada símbolo de entrada, seu próximo estado pode ser um de dois ou mais estados possíveis. Assim, na definição formal, o próximo estado é um elemento do conjunto das partes dos estados. Este elemento, um conjunto, representa algum subconjunto de todos os possíveis estados que podem ser considerados.
Uma extensão do AFND é o AFND-lambda (também conhecido como AFND-epsilon ou como AFND com transições epsilon), o qual permite uma transição para um novo estado sem ler nenhum símbolo de entrada. Por exemplo, se está no estado 1, com o próximo símbolo de entrada um a, pode-se se mover para o estado 2 sem ler nenhum símbolo de entrada. Assim, tem-se uma ambiguidade: o sistema está no estado 1 ou no estado 2 antes de ler a entrada a? Por causa desta ambiguidade, é mais conveniente falar de um conjunto de possíveis estados que o sistema pode estar. Assim, antes de ler um símbolo de entrada a, o AFND-epsilon pode estar em qualquer um dos estados do conjunto {1,2}. Equivalentemente, pode-se imaginar que o AFND está nos estados 1 e 2 'ao mesmo tempo': isso dá uma ideia informal da construção do conjunto das partes: o AFD equivalente a um AFND é definido como aquele que está no estado q={1,2}. Transições para novo estados sem ler símbolos de entrada são chamadas de transições lambda ou transições epsilon. Elas são comumente rotuladas com as letras gregas λ ou ε.
A noção de aceitação de uma entrada é similar àquela em um AFD. Quando o último símbolo de entrada é lido, diz-se que o AFND aceita se e somente se existe algum conjunto de transições que irá levá-lo a um estado de aceitação. Do mesmo modo, ele rejeita, se, não importando quais transições sejam aplicadas, ele não terminar num estado de aceitação.

## Definição formal

Dois tipos similares de AFNDs são comumente definidos: o AFND e o AFND com transições ε. O tipo comum é definido como uma 5-tupla, $M=(Q,\Sigma ,\delta ,q_{0},F)\,\!$, consistindo de
* um conjunto finito de estados $Q$;
* um conjunto finito de símbolos de entrada $\Sigma$, denominando o alfabeto;
* uma função de transição $\delta :Q\times \Sigma \rightarrow$ ${\mathcal {P}}(Q)$;
* um estado inicial $q_{0}\in Q$;
* um conjunto distinto de estados $F$ como estados de aceitação (ou estados finais), $F\subseteq Q$.
Aqui, ${\mathcal {P}}(Q)$ denota o conjunto das partes de $Q$
O AFND com transições ε (também conhecido como AFN-epsilon ou AFN-lambda) é definido por uma 5-upla extremamente semelhante, porém, a função de transição é substituída por uma que permite a cadeia vazia ε como uma entrada possível. Desse modo, sua função de transição é dada por:
$\delta :Q\times (\Sigma \cup \{\epsilon \})\rightarrow$ ${\mathcal {P}}(Q)$
Pode-se mostrar que AFND comum e um AFND com transições ε são equivalentes e, assim, dado qualquer AFND comum, pode-se construir um AFND com transições ε que reconhece a mesma linguagem e vice-versa.

## Propriedades dos AFNDs

A máquina começa no estado inicial especificado e lê uma cadeia de símbolos do seu alfabeto. O autômato usa a função de transição de estado $\delta$ para determinar o próximo estado usando o estado atual e o símbolo que acabou de ser lido, o que inclui a cadeia vazia, no caso dos AFNDs com transições ε. Entretanto, "o próximo estado de um AFND não depende somente do evento atual, mas também de um número arbitrário de eventos subsequentes de entradas. Até esses eventos subsequentes ocorrerem, não é possível determinar onde a máquina de estados está".
O conjunto de todas as cadeias que são aceitas por um AFND é a linguagem que o AFND aceita. Esta linguagem deve ser uma linguagem regular.
Para todo AFND pode ser encontrada um AFD que aceite a mesma linguagem. Portanto, é possível converter um AFND existente em um AFD com o propósito de implementar (talvez) uma máquina mais simples. Isto pode ser realizado usando a construção do conjunto das partes, o que pode levar a um aumento exponencial no número de estados necessários. Uma prova formal desta construção do conjunto das partes pode ser encontrada aqui.
Muitas vezes, pode ser mais fácil construir um AFND do que um AFD equivalente, tanto pelo entendimento como pelo tamanho da construção. AFNDs são comumente introduzidos no início de cursos de informática teórica para desenvolver a ideia de não-determinismo em modelos computacionais mais poderosos, como as máquina de Turing.

## Definição formal de computação dos AFNDs

Seja $N=(Q,\Sigma ,\delta ,q_{0},F)$ um AFND e $w\in \Sigma$ uma cadeia do alfabeto.
Diz-se que $N$ aceita $w$ se podemos escrever $w$ como $w=y_{1}y_{2}...y_{n}$ onde cada $y_{i}\in \Sigma _{\epsilon }$ e existe uma sequência de estados $r_{0}r_{1}..r_{n}$ em $Q$ seguindo as três condições a seguir:
* $r_{0}=q_{0}$;
* $r_{i+1}\in \delta (r_{i},y_{i+1})$ para $i=0,1,...,n-1$;
* $r_{n}\in F$.

## Propriedades do AFND-ε

Para todo $p,q\in Q,$ escreve-se $p{\stackrel {\epsilon }{\rightarrow }}q$ se e somente se $q$ pode ser alcançado a partir de $p$ através de zero ou mais transições $\epsilon$. Em outras palavras, $p{\stackrel {\epsilon }{\rightarrow }}q$ se e somente se existe $q_{1},q_{2},\cdots q_{k}\in Q$ onde $k\geq 0$ tal que
    $q_{1}\in T(p,\epsilon ),q_{2}\in T(q_{1},\epsilon ),\cdots q_{k}\in T(q_{k-1},\epsilon ),q\in T(q_{k},\epsilon )$.
Para qualquer $p\in Q$, o conjunto de estados que podem ser alcançados a partir de p é chamado de fecho-epsilon ou fecho-ε de p, e é escrito como
    $E(\{p\})=\{q\in Q:p{\stackrel {\epsilon }{\rightarrow }}q\}$.
Para qualquer subconjunto $P\subset Q$, define-se o fecho-ε de P como
    $E(P)=\bigcup \limits _{p\in P}E(\{p\})$.
As transições ε são transitivas, de modo que pode ser demonstrado que, para todo $q_{0},q_{1},q_{2}\in Q$ e $P\subset Q$, se $q_{1}\in E(\{q_{0}\})$ e $q_{2}\in E(\{q_{1}\})$, então $q_{2}\in E(\{q_{0}\})$.
Igualmente, se $q_{1}\in E(P)$ e $q_{2}\in E(\{q_{1}\})$ então $q_{2}\in E(P)$
Seja $x$ uma cadeia sobre o alfabeto $\Sigma \cup \{\epsilon \}$. Um AFND-ε $M$ aceita a cadeia $x$ se existe tanto uma representação de $x$ na forma $x_{1}x_{2}...x_{n}$, onde $x_{i}\in \{\Sigma \cup \epsilon \}$, como uma sequência de estados $p_{0},p_{1},...,p_{n}$, onde $p_{i}\in Q$, seguindo as seguintes condições:
* $p_{0}\in E(\{q_{0}\})$;
* $p_{i+1}\in E(\delta (p_{i},x_{i+1}))$ para $i=0,1,...,n-1$;
* $p_{n}\in F$.

## Implementação

Existem várias maneiras de implementar um AFND:
* Converter para o AFD equivalente. Em alguns casos isso pode causar um aumento exponencial no tamanho do autômato e, assim, espaço proporcional auxiliar ao número de estados do AFND (como o armazenamento do estado exige no máximo um bit para cada estado no AFND).
* Manter uma estrutura do conjunto de dados de todos os estados em que a máquina poderia estar. Na leitura do ultimo simbolo de entrada, se um desses estados é um estado final, a máquina aceita a cadeia. No pior dos casos, isso pode exigir espaço auxiliar proporcional ao número de estados do AFND; se a estrutura de conjunto usa um bit por estado do AFND, então esta solução é exatamente equivalente a anterior.
* Criar múltiplas cópias. Para cada n maneiras de decidir, o AFND cria até $n-1$ cópias da máquina. Durante sua computação, caso o AFND se depare com uma situação na qual existam múltiplas maneiras de se prosseguir, isto é, a função de transição leve a mais de um estado, pode-se considerar que o AFND divide-se em múltiplas cópias e cada uma dessas cópias fica responsável pela computação de uma das transições possíveis. Quando alguma cópia da máquina chega a uma situação na qual não há transição possível, diz-se que essa cópia do AFND "morre", não mais fazendo parte da computação. Se forma semelhante, caso alguma das cópias do AFND esteja em um estado de aceitação ao final da computação da cadeia de entrada, diz-se que o AFND aceita a dada cadeira. Isso também requer armazenamento linear com respeito ao número de estados do AFND, como também pode haver uma máquina para cada estado do AFND. Dessa forma, pode-se pensar no não-determinismo dos AFNDs como uma computação paralela, na qual o AFND (análogo a um processo) pode se bifurcar (de forma análoga à chamada de sistema Fork).
* Árvore de possibilidades. Derivada da ideia de ramos de computação, nessa forma, representamos a computação a partir de uma árvore de possibilidades cuja raiz corresponde ao início da computação. A cada símbolo de entrada lido, analisa-se a transição e se criam nós filhos adequadamente, isto é, caso haja 2 estados possíveis em uma dada transição, a árvore ramifica no nível atual e criam-se 2 novos filhos. Analogamente, se houver apenas uma transição possível, a árvore não é ramificada no dado momento e se não houver transição possível, o ramo "morre" e não é mais levado em conta na computação.
* Explicitamente propague tokens através da estrutura de transição do AFND e combine sempre que um token alcançar um estado final. Isto, às vezes, é útil quando o AFND pode codificar contextos adicionais sobre os eventos que desencadearam da transição. (Para uma implementação que usa esta técnica acompanhe as referências e olhe os Tracematches.)

## Exemplo

* Exemplo 1: O seguinte exemplo explica um AFND M, com um alfabeto binário, que determina se uma entrada contém um número par de 0s ou um número par de 1s (Note que 0 ocorrências é um número par de ocorrências). Seja M= (Q, Σ, T, s$_0$, F) onde
* Σ = {0, 1},
* Q= {s$_0$, s$_1$, s$_2$, s$_3$, s$_4$},
* E({s$_0$}) = { s$_0$, s$_1$, s$_3$ }
* F= {s$_1$, s$_3$}, and
* A função de transição T pode ser definida por esta tabela de transição de estados:
| | 0 | 1 | ε |
| S$_0$ | | | {S$_1$, S$_3$} |
| S$_1$ | {S$_2$} | {S$_1$} | |
| S$_2$ | {S$_1$} | {S$_2$} | |
| S$_3$ | {S$_3$} | {S$_4$} | |
| S$_4$ | {S$_4$} | {S$_3$} | |
O diagrama de estados para M é:
M pode ser visto como a união de dois AFDs: um com os estados {S$_1$, S$_2$} e o outro com os estados {S$_3$, S$_4$}.
A linguagem de M pode ser descrita pela linguagem regular dada por esta expressão regular $(1^{*}(01^{*}01^{*})^{*})\cup (0^{*}(10^{*}10^{*})^{*})$.
* Exemplo 2:
Criar o diagrama do AFND da linguagem L = {w | w termina em 00} com 3 estados. Resposta: O estado inicial é de não aceitação e fica em loop recebendo 1's e 0's até que ele "adivinha" de forma não determinística que um 0 recebido é o penúltimo ﻿0 da cadeia, e assim, passa para o segundo estado, que por sua vez, recebe o último zero e passa para o estado final de aceitação, que não tem nenhuma transição partindo dele, e caso recebe um outro símbolo, o ramo da computação morre.
(Exemplo de exercícío não resolvido do Sipser).
* Exemplo 3:
Criar o diagrama do AFND da linguagem U = { w | w é 0* } com 1 estado. Resposta: O estado inicial é o estado de aceitação e tem apenas 1 transição, que é para ele mesmo, no caso de ler o símbolo 0 da entrada. Caso outro símbolo seja lido, o ramo da computação morre. Neste caso, o não determinismo está associado a não ter uma transição para todos os símbolos do alfabeto. Fazendo um comparativo com o AFD, seria necessário criar um outro estado que é alcançado caso o estado inicial leia 1 da entrada, e que possui um loop para ele mesmo ao receber 0's e 1's.﻿
(Exemplo de exercício não resolvido do Sipser).
* Exemplo 4:
Criar do diagrama do AFND da linguagem A = { w | w = {0}} com 2 estados.﻿ Resposta: O estado inicial é estado de não aceitação e tem uma transição para o estado final de aceitação ao ler o símbolo 0 da entrada. O estado final de aceitação não possui transições para nenhum outro estado. Caso o estado inicial leia o símbolo 1 da entrada ou o estado final ler 0 ou 1, o ramo da computação morre.﻿
(Exemplo de exercício não resolvido do Sipser).﻿
* Exemplo 5:
Criar o diagrama do AFND da linguagem B = { w | w = vazio } com 1 estado.﻿ Resposta: Só há 1 estado que é o estado de aceitação. Não há nenhuma transição. Se o estado receber qualquer símbolo, o ramo da computação morre.﻿
(Exemplo de exercício não resolvido do Sipser).﻿

## Aplicação de AFND-ε

AFNDs e AFDs são equivalentes tal que se uma linguagem é reconhecida por um AFND, ela também é reconhecida por um AFD e vice versa. O estabelecimento de tal equivalência é importante e útil. É útil porque construir um AFND que reconhece uma dada linguagem é algumas vezes mais fácil do que construir um AFD para aquela linguagem. Isso é importante porque AFNDs podem ser usadas para reduzir a complexidade do trabalho matemático necessário para estabelecer muitas propriedades importantes na teoria da computação. Por exemplo, é muito mais fácil provar as propriedades que seguem usando AFNDs do que AFDs:
* A união de duas linguagens regulares é regular:
A ideia da prova parte do princípio de que, tendo duas linguagens regulares e os AFNDs que as reconhecem, podemos tomar os dois AFNDs e combiná-los com um terceiro AFND que aceita sua entrada de qualquer um dos dois AFNDs prévios a aceita. A prova formal pode ser encontrada aqui.
* A concatenação de duas linguagens regulares é regular:
Sendo dois AFNDs iniciais $A_{1}$ e $A_{2}$, a ideia da prova consiste em construir um novo AFND $A_{concat}$ usando a estrutura de $A_{1}$ e $A_{2}$ na construção. O novo AFND possui como estado inicial, o estado inicial do AFND $A_{1}$ e possui toda a estrutura de $A_{1}$, com os estados de aceitação de $A_{1}$ transformados em estados de não-aceitação possuidores de transições $\epsilon$ para o estado inicial de $A_{2}$. O novo AFND, por conseguinte, também possui toda a estrutura de $A_{2}$, e seus estados de aceitação são os mesmos de $A_{2}$. Dessa forma, a entrada sempre poderá ser quebrada em duas partes não-deterministicamente, e só será aceita se puder ter a primeira parte aceita por $A_{1}$ e a segunda por $A_{2}$.
* O Fecho de Kleene de uma linguagem regular é regular:
Sendo o AFND inicial $A_{1}$, é construído um novo AFND $A_{*}$ que modifica a estrutura de $A_{1}$ para fazer com que os estados de aceitação deste possuam, cada um, uma transição $\epsilon$ para o estado inicial de $A_{1}$. Adicionalmente, cria-se um novo estado de aceitação que passa a ser o estado inicial de $A_{*}$, este possuindo uma transição $\epsilon$ para o estado inicial antigo (estado inicial de $A_{1}$).
É importante notar que a construção de $A_{*}$ a partir da mera adição do estado inicial de $A_{1}$ ao conjunto dos estados de aceitação não provaria o que é proposto, pois há casos nos quais certas cadeias indesejadas seriam aceitas. Um exemplo de linguagem cuja computação nessa construção leva a uma falha é a linguagem $1(01)^{*}$.