Teoria da Computação
Pablo Azevedo Sampaio Wilson Rubens Galindo Wilson Rosa de Oliveira Júnior
Volume 1
Recife, 2009
Universidade Federal Rural de Pernambuco
Reitor: Prof. Valmar Corrêa de Andrade Vice-Reitor: Prof. Reginaldo Barros Pró-Reitor de Administração: Prof. Francisco Fernando Ramos Carvalho Pró-Reitor de Extensão: Prof. Paulo Donizeti Siepierski Pró-Reitor de Pesquisa e Pós-Graduação: Prof. Fernando José Freire Pró-Reitor de Planejamento: Prof. Rinaldo Luiz Caraciolo Ferreira Pró-Reitora de Ensino de Graduação: Profª. Maria José de Sena Coordenação Geral de Ensino a Distância: Profª Marizete Silva Santos Produção Gráfca e Editorial
Capa e Editoração: Allyson Vila Nova, Rafael Lira e Italo Amorim Revisão Ortográca: Marcelo Melo Ilustrações: Allyson Vila Nova Coordenação de Produção: Marizete Silva Santos
Universidade Federal Rural de Pernambuco
Reitor: Prof. Valmar Corrêa de Andrade Vice-Reitor: Prof. Reginaldo Barros Pró-Reitor de Administração: Prof. Francisco Fernando Ramos Carvalho Pró-Reitor de Extensão: Prof. Paulo Donizeti Siepierski Pró-Reitor de Pesquisa e Pós-Graduação: Prof. Fernando José Freire Pró-Reitor de Planejamento: Prof. Rinaldo Luiz Caraciolo Ferreira Pró-Reitora de Ensino de Graduação: Profª. Maria José de Sena Coordenação Geral de Ensino a Distância: Profª Marizete Silva Santos Produção Gráfca e Editorial
Capa e Editoração: Allyson Vila Nova, Rafael Lira e Italo Amorim Revisão Ortográca: Marcelo Melo Ilustrações: Allyson Vila Nova Coordenação de Produção: Marizete Silva Santos
Sumário Capítulo 1 – Introdução ...................................................................................4
1.1 Alfabetos, Cadeias e Linguagens ........................................... ..................... ...................... ............. 5 1.2 Operações sobre cadeias..................................................................10 1.3 Operações sobre linguagens.............................................................14 Capítulo 2 – Autômatos Finitos Determinísticos Determinísticos ........................................ ..................... ................... 18
2.1 Explicação Inicial ...............................................................................18 2.2 Denição Formal ...............................................................................25 2.3 Criando AFDs ....................................................................................32 Capítulo 3 – Autômatos Finitos Não-Determinísticos Não-Determinísticos ................................ ..................... ........... 42
3.1 Explicação Inicial ...............................................................................42 3.2 Denição Formal ...............................................................................45 3.3 Relação entre AFD e AFND...............................................................46 3.4 Um novo tipo de transição (ε (ε) ............................................................51 Considerações Finais ....................................................................................60
Teoria da Computação
Capítulo 1 – Introdução Olá, cursista! Em disciplinas passadas, você deve ter aprendido a criar programas para resolver problemas os mais variados. Nessa sua experiência com a programação, talvez você já tenha tido a impressão de que é possível criar programas para qualquer problema matemático que se possa imaginar. Será que isso é mesmo verdade? Será que todo problema matemático pode ser resolvido com algum programa de computador? Essa questão, que envolve tanto a Matemática quanto a Computação, é uma das principais questões teóricas que abordaremos neste curso de Teoria da Computação. Para chegarmos a uma resposta para ela, nós vamos estudar modelos matemáticos conhecidos como modelos computacionais. Veremos que, com cada modelo, somos capazes de criar diversos “computadores” teóricos (ou “programas” teóricos), onde cada computador resolve algum problema matemático especíco. Veremos vários modelos computacionais diferentes. Cada novo modelo apresentado será, em geral, capaz de resolver mais problemas matemáticos do que os modelos anteriores. Muitos dos modelos computacionais têm aplicação prática em áreas como: buscas em texto, validação de entradas, criação de linguagens de programação e criação de compiladores. No decorrer do curso falaremos um pouco de cada uma dessas aplicações práticas, mas somente quando chegarmos ao último modelo computacional (o modelo Máquina de Turing) é que poderemos responder a questão teórica levantada acima. Ou seja, somente com o modelo Máquina de Turing seremos capazes de chegar a conclusões teóricas sobre a real capacidade de resolver problemas dos computadores reais construídos pelo homem. Antes de começarmos a ver os modelos computacionais, precisamos introduzir alguns conceitos importantes que usaremos em todo o material. Esses conceitos básicos serão apresentados no decorrer deste primeiro capítulo, enquanto os modelos computacionais serão vistos somente a partir do segundo capítulo.
4
Teoria da Computação
1.1 Alfabetos, Cadeias e Linguagens
Neste início do curso, nós vamos desviar um pouco do termo problema matemático para falar no termo linguagem formal. Para a Teoria da Computação (e para a Matemática) esses dois conceitos podem assumir signicados parecidos. Poderíamos, na verdade, usar apenas um deles neste curso de Teoria da Computação, deixando o outro de lado. Por isso, o que faremos será dar destaque ao termo linguagem formal , deixando o termo problema matemático um pouco mais de lado. À primeira vista, pode não fazer muito sentido para você como essas duas palavras podem signicar a mesma coisa, mas não se assuste! Aqui, não estamos preocupados com o signicado comum das palavras linguagem e problema, mas com o signicado matemático dado a elas dentro da Teoria da Computação! A denição matemática de linguagem formal depende de outros conceitos matemáticos importantes, que são: alfabeto, símbolos e cadeias. Apresentaremos a denição matemática desses conceitos, comparando cada um deles com o conceito similar presente nos estudos da língua humana escrita. Vamos começar relembrando o que sabemos sobre a escrita da língua portuguesa... No início das nossas vidas de estudantes, nós aprendemos as letras que formam o nosso alfabeto: A, B, C, D, ..., Z. Depois aprendemos a usar essas letras para formar as palavras e frases do nosso dia-adia. Esse conjunto de palavras e frases que usamos forma a nossa língua: a Língua Portuguesa. Outras línguas do mundo se diferenciam da nossa por terem palavras e frases diferentes. Algumas dessas línguas são escritas com o mesmo alfabeto que a nossa (como a língua espanhola) e outras usam um alfabeto completamente distinto (como a japonesa). Do estudo das línguas humanas, a Matemática tomou emprestados os termos de alfabeto, cadeia e linguagem. A Matemática deu sua própria denição a cada um desses termos, criando conceitos mais gerais. Na visão matemática, um alfabeto é denido assim: Um alfabeto é um conjunto qualquer que usamos para criar linguagens.
Vamos considerar, neste curso, que um alfabeto só não pode ser innito nem vazio. Qualquer outro conjunto matemático pode ser 5
Teoria da Computação
usado como alfabeto. Exemplos de alfabetos (na denição matemática): A1 = { a, b, c, d } A2 = { 0, 1 } A3 = { x, +, (, *, ) } Nos três exemplos acima, temos alfabetos formados por letras, números e por outros sinais grácos diversos. Outros tipos de elementos também poderiam ter sido usados para formar outros alfabetos. Para dar um nome comum aos elementos de qualquer tipo presentes nos alfabetos, foi adotado o termo símbolos (ao invés do termo letras, usado para os elementos dos alfabetos das línguas humanas). Bem, agora vamos falar do conceito matemático que seria o mais próximo do conceito de palavra (e também do conceito de frase): Uma cadeia é uma sequência de símbolos de um alfabeto.
Diferente das palavras nas línguas humanas, uma cadeia não precisa ter um signicado associado. Basta ser formada por símbolos que aparecem em sequência, podendo haver símbolos repetidos. Quando uma cadeia é criada com os símbolos de um alfabeto X dizemos que ela é uma “ cadeia sobre X ”. São exemplos de cadeias sobre A 1: acaba, aba, abbccc, daacbbb. São exemplos de cadeias sobre A 2: 0, 1000, 0011, 1101000. São exemplos de cadeias sobre A 3: x*x, (x+x), **x*, )x**. Veremos agora um tipo especial de cadeia que não tem similar em linguagens humanas. (Se fosse possível, este tipo de cadeia seria como uma palavra vazia, sem nenhuma letra). Essa cadeia é a cadeia vazia: A cadeia vazia é uma cadeia que não tem nenhum símbolo.
Este tipo de cadeia pode ser formado com qualquer alfabeto e será uma cadeia bastante usada no restante do curso. Pelo fato de não ter símbolo nenhum, não temos como representar a cadeia vazia explicitamente. Por isso, usaremos a letra grega e (épsilon) especialmente com essa nalidade. 6
Teoria da Computação
Atenção Para evitar confusão, o símbolo e não aparecerá como símbolo de nenhum alfabeto neste material. Um e deverá sempre ser entendido como a total ausência de símbolos, ou seja, uma cadeia vazia. Assim, a cadeia 0 sobre o alfabeto A2 é uma cadeia com um (único) símbolo, mas e sobre o alfabeto A2 é uma cadeia que não tem símbolo nenhum.
Agora que já denimos cadeias, podemos partir para o último (e mais importante) conceito desta seção: o conceito matemático de linguagem. Este conceito é denido simplesmente assim: Uma
linguagem
formal (ou
simplesmente
uma
linguagem ) é um conjunto de cadeias.
Esta denição também tem relação com as línguas humanas, pois cada língua humana tem um conjunto de palavras e de frases distinto. Portanto, podemos pensar que “uma língua humana é o conjunto de palavras e frases que ela permite”. Se trocarmos “palavras e frases” por “cadeias”, realmente, chegamos à mesma denição apresentada acima. Cada linguagem formal é formada de cadeias de um único alfabeto. Se as cadeias forem formadas com um alfabeto X, dizemos que a linguagem é uma “linguagem sobre X ”. Alguns exemplos de linguagens nitas sobre o alfabeto A2: L1 = { 00, 01, 100, 11 } L2 = { 1, 000 } Uma linguagem vazia sobre A 2: L3 = { } Veja que uma linguagem é meramente um conjunto matemático. Por isso, tudo que você já conhece sobre conjuntos vale aqui. Por exemplo, assim como nos conjuntos, existem linguagens que são fnitas, linguagens que são infnitas e linguagens que são vazias, dependendo da quantidade de cadeias que elas contêm. Demos exemplos de linguagens nitas e vazias. Um problema surge quando desejamos representar uma linguagem innita, porque é impossível listar todos os seus elementos. Por isso, muitas vezes deniremos uma linguagem innita dando apenas uma descrição 7
Teoria da Computação
informal dela. Consideramos que uma descrição é informal quando ela que não é dada completamente em notação matemática. Abaixo, damos um exemplo de uma descrição informal para uma linguagem innita: “Seja L4 a linguagem sobre A 1 que contém todas as cadeias que começam com o símbolo d (e não contém nenhuma cadeia além dessas).” Podemos dar uma descrição ainda mais simples da mesma linguagem assim: “L4 é a linguagem das cadeias sobre A 1 que começam com d .” Se já tiver sido dito, antes, que o alfabeto é A 1 e, também, se o nome da linguagem não importar, a descrição pode car ainda mais simples: “Cadeias que começam com d .” Veja que não conseguiríamos listar todos os elementos da linguagem L4, porque as elas têm que ter d como primeiro símbolo, mas, depois dele, podem ter qualquer quantidade de símbolos. Isso torna as possibilidades innitas, como você pode perceber ao tentar listar as cadeias dessa linguagem: L4 = { da, db, daa, dbb, dba, dac, dcaa, dadaa, dacddbd, ... } Esperamos que tenha cado claro pra você, aluno, que uma linguagem é meramente um conjunto. Por isso, tudo que você já aprendeu antes sobre conjuntos pode ter aplicação no estudo formal de linguagens. Esperamos que, depois de ter lido esta seção, você tenha compreendido bem estes três conceitos: alfabeto, cadeia e linguagem. A seguir, na seção “Aprenda Praticando” daremos a você a oportunidade de praticar esses conceitos.
Aprenda Praticando
Este é o momento de colocar em prática os seus conhecimentos! Para isso, acompanhe atentamente a primeira questão, que já está respondida, e, depois, responda a segunda questão. 8
Teoria da Computação
1) Nas
questões abaixo, B = { a, b, 0, 1 }.
considere
que
o
alfabeto
é
a. Mostre quatro cadeias sobre o alfabeto. Resposta: Entre outras cadeias, podemos citar:
e, a, a0a, b101a.
b. Crie duas linguagens nitas sobre o alfabeto. Resposta: Basta formar um conjunto com uma quantidade
limitada (nita) de cadeias. Existe uma innidade de linguagens assim, mas vamos indicar apenas duas aqui: { a, a0a, 1bb, 10 } { } c. Crie duas linguagens innitas sobre o alfabeto. Resposta: Para criar linguagens innitas, não será possível
simplesmente listar as cadeias. O que será preciso fazer é pensar em uma propriedade que innitas cadeias possuem. Então, basta citar que a linguagem é formada pelas cadeias com aquela propriedade. Também existe uma innidade de linguagens assim, mas vamos mostrar duas: “A linguagem que contém todas as cadeias que usam o símbolo 0 .” “A linguagem das cadeias que começam com a”. Veja que a primeira linguagem contém cadeias como a0a, b110, 0ab e innitas outras. Já a segunda contém cadeias como a, a0, ab, a11b e innitas outras. Agora é a sua vez de responder a segunda questão. Atenção para o alfabeto que é um pouco diferente do anterior. Isso muda as cadeias e, consequentemente, as linguagens que você pode formar. 2) Nas
questões C = { 1, 2, 3 }.
abaixo,
considere
que
a. Mostre quatro cadeias sobre o alfabeto. b. Crie duas linguagens nitas sobre o alfabeto. c. Crie duas linguagens innitas sobre o alfabeto.
9
o
alfabeto
é
Teoria da Computação
A partir de agora, veremos algumas operações importantes que podem ser realizadas sobre cadeias e linguagens. (Operações sobre alfabetos não são de grande utilidade, por isso não veremos).
1.2 Operações sobre cadeias
Esta seção apresenta algumas operações sobre cadeias que usaremos no decorrer do curso. Na denição das operações, vamos usar duas cadeias genéricas representadas assim: a1..an e b1..bm, onde os símbolos são representados genericamente por a (ou b) seguido de um número subscrito que representa a posição dele na cadeia. (Por exemplo, a3 representa o símbolo que aparece na terceira posição da cadeia a1..an). Nas explicações que daremos abaixo, você pode trocar essas duas cadeias por qualquer cadeia que desejar, inclusive pela cadeia vazia e. Para cada operação, mostraremos como chamamos a operação, depois como a representamos e, em seguida, explicamos detalhadamente o signicado dela. Tamanho da cadeia a1..an : | a1..an | = n
Esta operação dá a quantidade total de símbolos de uma cadeia, contando inclusive com símbolos que aparecem mais de uma vez. Essa operação é representada colocando a cadeia entre duas barras verticais “|”. Alguns exemplos: |a|=1 | aba | = 3 | 000 | = 3 |e|=0 Este último exemplo mostra o tamanho da cadeia vazia e. Como dissemos na seção anterior, essa cadeia não tem símbolo nenhum. Por isso, o seu tamanho será zero. Concatenção da cadeia a1..an com a cadeia b1..bm: a1..an . b1..bm = a1..anb1..bm
10
Teoria da Computação
ou a1..an b1..bm = a1..anb1..bm
Esta operação recebe duas cadeias e produz uma nova cadeia acrescentando, ao nal da primeira cadeia, todos os símbolos da segunda cadeia. Esta operação pode ser representada colocando-se um sinal de ponto “.” entre as cadeias ou, simplesmente, colocando as cadeias lado a lado sem o uso de nenhum sinal. Vejamos alguns exemplos: 01 . 10 = 0110 a . bb = abb e . 101 = 101
abba . e = abba Os dois últimos exemplos mostram uma propriedade interessante da concatenação: qualquer concatenação envolvendo a cadeia vazia e alguma outra cadeia dá como resultado sempre essa outra cadeia. Autoconcatenação k vezes da cadeia a1..an (para um k ≥ 0): (a1..an)k = (a1..an) (a1..an) … (a1..an)
(repete k vezes)
Esta operação faz concatenações entre k ocorrências repetidas da cadeia, para um valor natural k constante. A operação é representada colocando-se k como expoente na cadeia. O expoente k indica a quantidade de repetições da cadeia a serem consideradas.
Atenção Ao longo do curso, usaremos parênteses, na maioria das vezes, com o propósito de agrupar. Nesses casos, ele poderá ser ignorado. Os parênteses não poderão ser ignorados apenas em casos particulares, que serão devidamente explicados. Isso acontecerá quando os símbolos “)” e “(” zerem parte do alfabeto que estivermos usando.
Alguns exemplos da autoconcatenação são dados abaixo: (101)1 = 101 (01)3 = 01.01.01 = 010101 a(b3) = a(b.b.b) = abbb 11
Teoria da Computação
(baa)0 = e e2 = e.e = e
Os dois últimos exemplos mostram duas propriedades que merecem ser comentadas. A primeira delas está relacionada ao expoente zero: toda cadeia autoconcatenada zero vezes dá será sempre a cadeia vazia (independentemente de qual seja a primeira cadeia). Já o último exemplo ilustra a seguinte propriedade: a cadeia vazia autoconcatenada qualquer número k de vezes dá sempre ela própria (independente do valor do expoente k).
Subcadeia a1..an é subcadeia de b1..bm
ou a1..an não é subcadeia de b1..bm
Esta é uma operação especial que pode dar os resultados: verdadeiro ou falso. Ela dá verdadeiro apenas se a cadeia a1..an é igual a algum trecho contínuo da segunda cadeia b1..bm. Neste caso, podemos armar que “ a1..an é subcadeia de b1..bm”. Se não existir nenhum trecho contínuo de b1..bm que seja idêntico à cadeia a1..an, a operação dá o resultado falso. Dizemos, então, que “ a1..an não é subcadeia deb1..bm”. Você pode ter cado um pouco confuso com a explicação, mas alguns exemplos devem ajudar-lhe a entender. Examine os exemplos abaixo, relendo a explicação acima para tentar entender esta operação: aba é subcadeia de aabaa aaba é subcadeia de aabaa bb não é subcadeia de aabaa 0 é subcadeia de 100 01 não é subcadeia de 100 100 é subcadeia de 100 e é subcadeia de 100
Mais uma vez, colocamos, nos dois últimos exemplos, dois casos especiais da operação. O penúltimo exemplo mostra que a cadeia 12
Teoria da Computação
100 é subcadeia de 100. Na verdade, essa é uma propriedade geral: toda cadeia é subcadeia dela própria. Já no último exemplo, vemos a ocorrência de outra propriedade geral: a cadeia vazia é subcadeia de qualquer cadeia. As operações sobre cadeias serão muito importantes no restante do curso. Por isso, vamos praticar um pouco essas operações antes de passar para a última seção.
Aprenda Praticando
Acompanhe atentamente a primeira questão, que já está respondida, e, depois, tente responder a segunda questão. 1) Nas questões abaixo, mostre o resultado de cada operação. Considere que o alfabeto é { a, b, c }. a) aaa . bbc Resposta: aaabbc
b) e . bba Resposta: bba
c) (ca)3 Resposta: cacaca
d) (aaab)1 Resposta: aaab
e) ca é subcadeia de ccca? Resposta: verdadeiro
f) cca é subcadeia de ccba? Resposta: falso
2) Nas questões abaixo, mostre o resultado de cada operação. Considere que o alfabeto é { 1, 2, 3 }. a) 1 . 223 b) 3212 . e 13
Teoria da Computação
c) (11)2 d) (123)0 e) 11 é subcadeia de 2131? f) 221 é subcadeia de 221?
1.3 Operações sobre linguagens
Veremos, agora, as operações sobre linguagens que serão mais importantes neste curso. Lembre-se, amigo(a) cursista, que as linguagens são meramente conjuntos de cadeias. Isso signica que todas as operações que você já aprendeu sobre conjuntos podem ser aplicadas às linguagens. De fato, duas das três operações que veremos serão meras operações de conjuntos que você já conhece. Apenas a última operação será realmente novidade para quem já aprendeu conjuntos. Mostraremos como representar cada operação usando as letras L e M simbolizando duas linguagens quaisquer. União de L e M:
LυM Esta operação é a mesma que vocês estudaram para conjuntos. O resultado da operação é uma linguagem (conjunto) que contém todas as cadeias de L e todas as cadeias de M, e nenhuma cadeia além dessas. Quer dizer, basta uma cadeia estar em L (ou em M) para fazer parte do resultado. Exemplos: { 1, 11 }
υ
{ 0, 11 }
= { 0, 1, 11 }
{ bab, aa, bb }
υ
{ aa, bb }
= { bab, aa, bb }
{ 01, 10 }
υ
{ 01, 10 }
= { 01, 10 }
{ aa, b }
υ
{ }
= { aa, b }
Todas as propriedades da união de conjuntos se aplicam à união linguagens. Por exemplo: a união de uma linguagem consigo mesma dá como resultado ela própria (ver penúltimo exemplo). Outra propriedade que também vale é: a união de uma linguagem qualquer X com a linguagem vazia dá a própria linguagem X (ver último 14
Teoria da Computação
exemplo). Intersecção de L e M:
L∩M Esta é mais uma operação de conjuntos que usaremos com linguagens. O resultado dela é uma linguagem que contém apenas as cadeias que estão em L e em M. Observe que, diferente da união, uma cadeia precisa pertencer às duas linguagens para fazer parte do resultado! Alguns exemplos dessa operação: { aa, a }
∩
{ a, bb }
= {a}
{ 0, 1, 11, 001 }
∩
{ 0, 00, 11 } = { 0, 11 }
{ 01, 111 }
∩
{ }
{ aaa, aa }
∩
{ bb, b, bbb } = { }
= { }
Concatenação de L com M:
L○M Diferentemente das outras duas operações sobre linguagens, esta não foi tirada do estudo dos conjuntos matemáticos. Esta operação sobre linguagens é denida a partir da operação de “concatenação” para cadeias (lembra que vimos na seção anterior?). A operação sobre linguagens é representada por um pequeno círculo, lembrando um pouco o ponto usado para a concatenação de cadeias. O que a operação L ○ M faz é aplicar a concatenação entre cadeias várias vezes. A cada vez, é feita a concatenação de uma cadeia de L com uma cadeia de M (nesta ordem), gerando uma outra cadeia. O conjunto de todas as cadeias que você puder gerar assim será o resultado da operação L ○ M. Alguns exemplos: { aa, bb }
○
{c}
= { aac, bbc }
{c}
○
{ aa, bb }
= { caa, cbb }
{ 01, 11 }
○
{ aab, b }
= { 01aab, 01b, 11aab, 11b }
{ }
={ }
{ 101, 000 } ○
15
Teoria da Computação
Vamos explicar o primeiro exemplo, para que que claro como calcular essa operação. Primeiro concatenamos aa (da primeira linguagem) com c (da segunda linguagem), dando o resultado aac . Depois, concatenamos bb (da primeira) com c (da segunda), dando a nova cadeia bbc . Como não temos mais opções, terminamos. Veja que, no exemplo, a linguagem de resultado contém exatamente as duas cadeias geradas: aac e bbc . Com base nessa explicação, tente entender os demais exemplos. Agora, vamos destacar algumas propriedades, com base nos exemplos acima. A primeira propriedade que queremos destacar é que a ordem em que duas linguagens são concatenadas pode fazer diferença no resultado. Veja que, nos dois primeiros exemplos, aplicamos a operação de concatenação com as mesmas duas linguagens, mudando apenas a ordem em que elas são concatenadas. Por conta dessa simples mudança de ordem, o resultado deu diferente nos dois primeiros exemplos. Outra propriedade interessante pode ser vista no penúltimo exemplo, onde temos a concatenação da linguagem { 101, 000 } com a linguagem vazia { }. A propriedade é que a concatenação de qualquer linguagem com a linguagem vazia dá como resultado a linguagem vazia. A explicação é que não há nenhuma cadeia na linguagem vazia,
então, não temos fazer nenhuma concatenação envolvendo cadeias das duas linguagens. Logo, a linguagem resultante não terá nenhuma cadeia.
Aprenda Praticando
Como das vezes anteriores, a primeira questão já está r espondida. Analise-a e, depois, tente responder a segunda questão. 1) Nas questões abaixo, compute o resultado de cada operação. O alfabeto usado é { a, b, c }. a) { a, aa } o { bc, cc } Resposta: { abc, acc, aabc, aacc }
b) { b, cab, baba } υ { cab, cc } Resposta: { b, cab, baba, cc } 16
Teoria da Computação
c) { b, cab, baba } ∩ { cab, cc } Resposta: { cab }
2) Nas questões abaixo, compute o resultado de cada operação. O alfabeto, dessa vez, é { 0, 1, 2 }. a) { e, 11 } ○ { 11, 2, 2112 } b) { 1, 211, 211, 0022 } υ { 00, 211, 211, 0022 } c) { 1, 211, 211, 0022 } ∩ { 00, 211, 0022 }
17
Teoria da Computação
Capítulo 2 – Autômatos Finitos Determinísticos No capítulo anterior, apresentamos um conceito central neste curso, que é o conceito de linguagem. Vimos que uma linguagem pode ser vazia, nita ou innita. As linguagens innitas, em especial, não foram representadas de maneira completamente matemática – elas foram representadas por descrições informais. A partir deste capítulo, veremos modelos matemáticos que permitirão representar linguagens (tanto nitas quanto innitas) de um modo completamente formal , usando uma notação puramente matemática. A ideia chave consistirá em tratar a linguagem como um problema matemático e apresentar uma solução matemática para o problema. Por exemplo, ao invés da linguagem das “cadeias que começam com d ”, ”, pensaremos no problema de “testar quais as cadeias que começam com d ”. ”. Usando algum modelo computacional, poderemos criar um “computador abstrato” (ou um programa abstrato) para servir de solução do problema. Um computador abstrato, basicamente, indica o passo-a-passo que você deve seguir para testar uma cadeia qualquer. Cada modelo computacional permite criar inúmeros “computadores abstratos” (ou “programas abstratos”). No restante do curso, veremos, a cada momento, um novo modelo computacional. Depois de aprender como cada modelo funciona, você deverá ser capaz de usá-lo para representar um grande número de linguagens, tanto nitas como innitas. Ou, em outras palavras, cada modelo servirá para resolver um grande número de problemas matemáticos. Neste capítulo, veremos o primeiro modelo computacional do curso: o modelo de Autômatos Finitos Determinísticos, ou, apenas, modelo AFD. Esse é um modelo simples que servirá como base para você entender outros modelos que veremos posteriormente.
2.1 Explicação Inicial
O nome autômato é usado para indicar algo que “se move sozinho”. No caso dos Autômatos Finitos Determinísticos, esse movimento não é físico, mas é apenas uma mudança abstrata de estado. Podemos 18
Teoria da Computação
pensar em um estado como a “situação” em que o autômato pode estar. O autômato muda de estado apenas diante de estímulos externos, que chamaremos de entradas do autômato. Para entender o funcionamento de um autômato, tome um procedimento que você faz com certeza em casa: acender uma luz. Para isso, você normalmente deve pressionar o interruptor existente do ambiente. A função função do interruptor pode ser modelada por autômatos e é um dos problemas mais fáceis de serem modelados. Podemos tomar um autômato nito na forma de um grafo direcionado (conjunto de pontos ligados ou não por setas) para representar o problema. Como você bem sabe, um interruptor pode estar “ligado” ou “desligado”. Ou seja, essas são as “situações” ou os “estados” em que você pode encontrar um interruptor. No grafo, representaremos os estados como círculos, e dentro de cada círculo você pode colocar um nome para melhor identicar o que o estado signica. Mas, anal, quando um interruptor pode mudar de estado? Apenas quando ele recebe um estímulo externo (ou uma entrada). No caso do interruptor, a entrada será o ato de pressionar . As mudanças entre os estados são representadas, no grafo, como setas acompanhadas da entrada que causa a mudança. O grafo que representa o autômato do interruptor pode ser visto abaixo:
Figura 1 – Sistema de um botão liga/desliga
Todo autômato autômat o nito tem um estado inicial indicado por um triângulo ou uma seta. Podemos também usar a notação de uma seta com o nome início. No autômato acima, assumimos que o estado inicial é “desligado”. Ao pressionar o botão do interruptor, o mesmo passa para o estado de ligado. E permanece assim até que o interruptor seja pressionado novamente, o que o faz voltar para o estado desligado. Bem, o exemplo do interruptor deve ter feito você entender um autômato, mas talvez ainda não tenha esclarecido como ele pode representar uma linguagem. Os autômatos nitos determinísticos 19
Teoria da Computação
(AFDs) serão autômatos cujas entradas são os símbolos vindos da cadeia que você deseja testar. Os símbolos da cadeia são lidos da esquerda para direita. Quando chega ao m da cadeia, dependendo do estado em que terminou, o autômato irá aceitar ou rejeitar a cadeia. Vamos considerar que “aceitar” indica que a cadeia faz parte da linguagem que o autômato representa. Vejamos agora um exemplo simples de um AFD: ele reconhece apenas a palavra “amor”. Nosso sistema deve ter a capacidade de responder quando a cadeia dada for exatamente como ele estava programado para aceitar. Em outras palavras, qualquer cadeia que não seja a palavra amor deverá ser rejeitada. Cada ação será a leitura de um símbolo. A representação gráca do autômato seria esta:
Figura 2 – Autômato para reconhecer a palavra amor
Em relação ao primeiro autômato, note que, basicamente, a única novidade foi a presença de um círculo duplo no estado q 4. Esta notação indica o estado de aceitação ou estado fnal. Podemos considerar que os outros estados são todos estados de “não aceitação”. Quando o autômato termina de ler uma cadeia e para no estado de aceitação, se diz que o autômato aceita ou reconhece a cadeia. Já se ele não conseguir ler a cadeia inteira ou se ele não parar em um estado de aceitação, dizemos que o autômato rejeita a cadeia. Para ilustrar o funcionamento do autômato, vamos analisar alguns exemplos. Queremos saber como o autômato vai se comportar lendo as seguintes cadeias: ambiente, amora e amor. Começando com a palavra ambiente, o autômato começa do estado q0, e só muda de estado quando lê a, que é justamente a primeira letra de nossa cadeia, então temos:
20
Teoria da Computação
Partimos para a segunda letra:
Depois, para a letra b:
Neste caso, o autômato para por não haver mais possibilidades de sair de q2. Como este não é um estado nal, concluímos que o autômato rejeitou a cadeia ambiente. Vejamos outro exemplo com a palavra amora:
Agora lendo m:
Lendo o:
21
Teoria da Computação
Lendo r :
Lendo a:
O autômato para novamente, só que, neste caso muito interessante, note que estamos no estado de aceitação, mas o autômato não tem opção para o último símbolo, deixando o símbolo a sem ser lido. Como dissemos antes, se a cadeia não for lida inteira, ela é rejeitada pelo autômato. Portanto, ele rejeita amora. Tomemos o exemplo da cadeia amor . Já temos a certeza que o autômato vai reconhecer esta cadeia como foi dito há pouco, mas devemos entender exatamente como isto acontece. Analisemos os passos a seguir: Lendo a:
Agora lendo m:
22
Teoria da Computação
Lendo o:
Lendo r :
Terminamos a leitura da palavra amor, percorremos todos os caracteres da palavra a agora devemos observar o autômato. Note que estamos no estado q 4, que é justamente o estado de aceitação. Se as duas situações ocorrerem: terminamos de ler a cadeia e estamos no estado de aceitação, então reconhecemos (aceitamos) a palavra. Portanto, a cadeia amor foi aceita!
Você já pode entender que realmente o autômato nito que estamos trabalhando neste momento aceita apenas a palavra amor. E já deve ter concluído que uma cadeia só é reconhecida se, ao término da leitura da cadeia, o autômato estiver no estado nal. Talvez você esteja pensando que esses autômatos são simples demais e pouco poderosos reconhecedores de cadeias. Pode parecer para você que seria difícil fazer um autômato para reconhecer várias cadeias, por exemplo. Mais difícil ainda seria criar um autômato para uma linguagem innita (com innitas cadeias), não acha? Na verdade, não é tão difícil. Tudo depende de como você cria 23
Teoria da Computação
as mudanças de estados. Como exemplo, veja abaixo um autômato pequeno, mas capaz de reconhecer um conjunto innito de cadeias (ou seja, uma linguagem innita). Tente compreender como isso é possível, antes de ler o texto adiante.
Figura 3 – Autômato Finito que reconhece linguagem innita
Analisando com calma o AFD, notamos que ler qualquer quantidade de 0’s repetidamente não fará com que o autômato saia do estado inicial. Somente quando ler o primeiro símbolo 1, o AFD mudará para o estado nal. Se este for o último símbolo da cadeia, então ele aceita a cadeia. Exemplos de cadeias que são aceitas por esse autômato são: 1, 01, 00001. Faça o teste no autômato acima e comprove! Isso dará a você mais conança de que realmente o autômato aceita innitas cadeias. Uma vez que ele aceita innitas cadeias, tente descrever informalmente a linguagem aceita pelo autômato. Faça isso antes de ler o próximo parágrafo. Podemos dar a seguinte descrição informal da linguagem que esse AFD aceita: “todas as cadeias de nenhum ou mais símbolos 0’s seguidos de um único símbolo 1”. Relembrando o que dissemos no início do capítulo, nós vamos tratar uma linguagem como um problema, ou melhor, como a solução de um problema. Nesse caso, o problema seria “testar se uma cadeia de entrada tem nenhum ou mais símbolos 0’s seguidos de um único símbolo 1”. O AFD acima é uma solução para esse problema, pois ele representa como fazer, passo-a-passo, esse teste em uma cadeia. Essa é uma maneira indireta de representar uma linguagem, mas é uma maneira que usaremos bastante no decorrer do curso (tanto com o modelo AFD como com outros modelos). Por isso, tenha sempre em mente que um AFD representa uma linguagem. Representa indiretamente, mas representa. O único detalhe que ainda não esclarecemos foi o fato de que a 24
Teoria da Computação
representação da linguagem deveria ser puramente matemática (como dissemos no início deste capítulo). Porém, até agora, só vimos AFDs representados gracamente. Será que é possível representar um AFD de maneira puramente matemática? Sim, é possível. Veremos isso a seguir.
2.2 Definição Formal
Podemos denir matematicamente um autômato determinístico D por meio uma quíntupla (ou uma 5-tupla):
nito
D = (Q, ∑, δ, s, F), onde: » Q é o conjunto de estados, que deve ser nito. » ∑ (sigma) é o alfabeto de entrada, que contém os símbolos que podem ser usados nas cadeias testadas no autômato »
(delta) é a função de transição. É do tipo: Q x ∑ → Q. Isso signica dizer que, estando em um estado de Q e lendo um símbolo do alfabeto ∑, ela faz o autômato passar para outro estado de Q. δ
» s é o estado inicial. Ele é um elemento de Q (ou seja, s
∈
Q).
» F é um conjunto dos estados fnais ou estados de aceitação. Ele é um subconjunto de Q (ou seja, F ⊆ Q). Por ser um conjunto, pode haver mais de um estado nal, assim como também pode não haver nenhum. Veremos adiante autômatos com essa característica. Note também que não há restrições quanto ao estado inicial ser um dos estados nais. Já estamos acostumados a trabalhar com autômatos na forma gráca, mas vamos ver agora, por meio de exemplos, como passar da forma gráca para a forma de função e vice-versa. Primeiramente, vamos partir da representação gráca para a representação formal. Suponha o autômato para reconhecer se há um número ímpar de 0’s em cadeias formadas por 0’s e 1’s. Vamos chamá-lo de D 1. A forma gráca de D1 é dada abaixo:
25
Teoria da Computação
Figura 4 – Autômato que reconhece números ímpares de zeros
Talvez pareça difícil para você, neste momento, aceitar que esse autômato consegue testar qualquer cadeia se há uma quantidade ímpar de zeros, mas nós vamos esclarecer adiante. Na denição formal de D1, precisamos preencher os cinco parâmetros da quíntupla D1 = (Q, ∑, δ, s, F). O primeiro é o conjunto de estados Q, que será simplesmente {q0, q1}. Para o parâmetro seguinte, vamos observar o enunciado do autômato. Nós dissemos acima que só vão ser dados como entrada cadeias de 0’s e 1’s. Isso signica que o alfabeto é {0,1}. O estado inicial (parâmetro s) é aquele representado por uma seta, que é q 0. Quanto aos estados de aceitação, são representados gracamente por círculos duplos. No autômato acima, só tem um estado de aceitação, que é o estado q1. Assim, já podemos escrever a seguinte quíntupla: D1 = ({q0, q1}, {0, 1}, δ, q0, {q1}) Lembramos que, na representação formal, os estados de aceitação são dados por um conjunto. Por isso, o parâmetro F foi trocado pelo conjunto unitário {q1}. Já o estado inicial é sempre único, então não precisou ser representado por um conjunto. Mas você deve estar se perguntando: quem é o delta ( δ)? O delta é a função de transição. Há uma maneira simples de representá-la, que é usando uma tabela. Cada linha representa um estado (de origem), e cada coluna, um símbolo. Na célula formada pelo encontro entre um estado (origem) e um símbolo, coloca-se qual o estado para onde a seta aponta (destino), se houver seta para o símbolo em questão. A função de transição de D1, ainda sem as células preenchidas, é mostrada a seguir: δ q0 q1
26
0
1
Teoria da Computação
Agora, para preencher a tabela, devemos acompanhar o autômato da seguinte forma: estado em q0 e lendo 0 qual a ação devemos tomar? Olhando para o autômato, vemos que a ação é mudar para o estado q1, pois há uma seta saindo de q 0, tendo o símbolo 0 anotado sobre ela, e esta seta entra em q 1. Então, preenchemos a célula no encontro entre q0 e 0 com o valor q 1. Depois, estando em q0, lendo 1, o autômato permanece no mesmo estado. Marcamos então q 0 na tabela. Fazemos a mesma análise para o estado q 1 e obtemos a seguinte tabela: δ
0
1
q0
q1
q0
q1
q0
q0
A representação da função de transição δ usando tabelas é bastante prática e, por isso, será a mais usada. Porém, existem outras formas de representá-la, que são equivalentes. A seguir mostramos as transições do autômato D 1 usando outra representação formal: δ (q0,
0) = q1 (estando em q0, lendo 0, mude para o estado q 1)
δ (q0,
1) = q0
δ (q1,
0) = q0
δ (q1,
1) = q0
Vamos explicar essa segunda representação da função de transição (δ) com base na representação por tabela. Veja que usamos a letra grega delta (δ) como uma função que recebe dois argumentos (um estado e um símbolo) e retorna um valor (um estado). Você pode entender os dois argumentos como sendo a linha e a coluna da tabela. O valor seria o conteúdo da célula, na tabela. Por exemplo, o valor de δ (q0, 1) você pode achar procurando na linha q 0, coluna 1. Conra que o valor é q1. Na representação nal do AFD, basta usar uma das duas formas para representar a função de transição. Das duas, usaremos com mais frequência a representação com tabelas. Mas isso não signica que você não deva saber a segunda forma – nós usaremos essa forma mais adiante! Por isso, aprenda as duas. Pronto, com isso, concluímos a nossa primeira representação formal de um AFD. A representação completa, usando uma tabela 27
Teoria da Computação
para representar δ, ca assim: D1 = ({q0, q1}, {0, 1}, δ, q0, {q1}) δ
0
1
q0
q1
q0
q1
q0
q1
Veja que, neste AFD, a tabela cou completa, sem nenhuma célula vazia. Neste caso, dizemos que D 1 é um AFD completo. Vamos considerar que um AFD é completo se ele tem transições para todos os símbolos em todos os estados. Caso contrário, ele será chamado de parcial. Um exemplo de AFD parcial (não completo) é dado abaixo.
Figura 5 – Autômato Finito Determinístico parcial
Você já consegue entender por que ele é parcial? Uma dica: tente representar as transições dele com tabela para descobrir se alguma célula ca vazia. Como você deve observar, as células da linha q 1 cam todas vazias naquele AFD. Isso porque ele não tem transições saindo do estado q1 para nenhum símbolo. Desse modo, conrmamos que ele não é um AFD completo – é um AFD parcial. No decorrer do curso usaremos autômatos parciais sem problema. Basta considerar que, quando não houver transição para o próximo símbolo da cadeia, o autômato vai rejeitar a cadeia. Por exemplo, no AFD acima, se dermos a cadeia 11 como entrada, o que acontece? Ele começa em q0, então lê o primeiro 1 e vai para q1. Depois, ele lerá o segundo símbolo 1 e irá para qual estado? Em q 1 não tem transição para o símbolo 1! Por isso, a cadeia será rejeitada! Podemos converter um AFD parcial para um AFD completo de maneira relativamente simples: 1. Crie um novo estado, que será chamado de estado sugadouro. Para cada símbolo, crie uma transição deste estado para ele mesmo. No autômato da gura 5, poderíamos criar um estado q2 assim: 28
Teoria da Computação
2. Nos outros estados, se estiver faltando transição com algum símbolo, crie a transição apontando para o sugadouro. No autômato de exemplo, falta a transição 0 em q 0 e faltam as transições 0 e 1 em q 1, o que nos leva a acrescentar as transições para q2 assim:
O que acontecerá é que, em todos os casos em que não havia transição no primeiro autômato, haverá uma transição indo para o estado sugadouro, no novo autômato. Os dois autômatos são equivalentes, aceitando as mesmas cadeias e rejeitando as mesmas cadeias. A diferença é que, as cadeias que o primeiro autômato rejeitaria por não conseguir ler toda a cadeia, o novo autômato rejeitaria porque caria “preso” no estado sugadouro, depois de ler toda a cadeia. Agora que já vimos vários detalhes dos Autômatos Finitos Determinísticos (AFDs), é hora de parar um pouco para xar o que foi visto. Faremos isso por meio dos exercícios a seguir.
Aprenda Praticando
Observe o exercício resolvido e, depois, tente resolver outro parecido. 29
Teoria da Computação
1) Crie um AFD para representar a linguagem L = { acaba },
denida sobre o alfabeto { a, b, c }. Resposta:
A princípio, é trivial criar um AFD para reconhecer uma linguagem com uma só cadeia. Basta criar um estado inicial e, depois dele, criar um estado para cada símbolo da cadeia. Então, obtemos facilmente o autômato, que chamaremos de D2:
Mas a forma gráca do autômato é imprecisa e, por isso, não é considerada uma representação formal (do ponto de vista matemático) do autômato. A representação formal seria dada por uma tupla D2 = (Q, ∑, δ, s, F), onde: Q é facilmente identicável, pois basta listar os nomes representados entre círculos no autômato acima: {q 0, q1, q2, q3, q4, q5}. Quanto ao alfabeto (∑), já informamos no enunciado que seria { a, b, c }. O estado inicial (s) é aquele identicado por uma seta: o estado q0. Os estados nais ou de aceitação (F) são os estados com círculos duplos. No autômato, só tem um, o que dá esse conjunto: {q5}. Quando à função de transição ( δ), ela será construída na forma de tabela, como ensinado anteriormente. Pronto, agora temos todos os componentes necessários para montar nosso autômato: D2 = ({q0, q1, q2, q3, q4, q5}, {a, b, c}, δ, q0, {q5}), onde δ é dado por: δ
a
b
c
q0
q1
-
-
q1
-
-
q2
q2
q3
-
-
30
Teoria da Computação
q3
-
q4
-
q4
q5
-
-
q5
-
-
-
Veja que este é um autômato parcial, pois não tem transição, em cada estado, para todo símbolo. Poderíamos deixá-lo assim mesmo, mas, para praticar os conceitos, vamos criar o autômato completo equivalente. Para isso, criamos um novo estado q6 para ser o estado “sugadouro”. Depois, criamos as ligações que faltam no autômato acima, fazendo-as apontarem para q6. A forma gráca é mostrada abaixo:
Note que uma vez que o autômato chega em q 6, qualquer que seja o restante da cadeia, o autômato não sai mais do estado. Agora, podemos refazer a denição formal assim: D2 = ({q0, q1, q2, q3, q4, q5, q6}, {a, b, c}, δ, q0, {q5}), onde δ é dado por: δ
a
b
c
q0
q1
q6
q6
q1
q6
q6
q2
q2
q3
q6
q6
q3
q6
q4
q6
q4
q5
q6
q6
q5
q6
q6
q6
Veja que a tabela agora está toda preenchida, como é típico de um AFD completo. Em especial, todas as posições que não 31
Teoria da Computação
tinham valor (representadas por um “-” antes), agora têm o estado q6. 2) Crie um AFD para representar a linguagem L = { baba }, denida sobre o alfabeto { a, b }. Esta questão você deverá responder. Em caso de dúvidas, não deixe de consultar os tutores, por meio do ambiente de ensino.
2.3 Criando AFDs
Nos exercícios acima, construímos apenas AFDs para linguagens unitárias (isso é, linguagens de uma só cadeia). Nesta seção, deremos algumas dicas para ajudar você a construir AFDs para linguagens mais complexas, em especial, para construir AFDs para linguagens innitas. Na construção de um autômato, o grande segredo é fazer com cada estado tenha um signicado. Cada estado deve representar alguma propriedade importante da cadeia. Mas, parar escolher qual propriedade é importante de ser representada, você precisa analisar bem o problema. Para você entender melhor, vamos tomar como exemplo uma linguagem: “todas as cadeias que começam com 11, sobre o alfabeto {0 ,1}”. Em outras palavras, precisamos criar um autômato para “ testar se uma cadeia começa com 11”. Note que este é um caso de uma linguagem innita, o que não quer dizer que seja complexo resolver esse tipo de problema. O autômato precisa ter, pelo menos, um estado inicial. No caso, o estado inicial representa que “ ainda não foi visto nada da cadeia”. Outro estado que o autômato vai precisar é um que represente que “ o primeiro símbolo é 1”. E, ainda, um terceiro estado deverá representar que “os dois primeiros símbolos são 1”. Este terceiro estado representa justamente a propriedade que queremos testar, por isso, ele será estado de aceitação. Essas são as três únicas situações que interessam para o problema, portanto, precisaremos apenas desses três estados. Um detalhe importante é que os nomes que daremos aos estados não é importante. Temos dado os nomes q0, q1, ..., qn, aos estados dos AFDs, mas poderíamos chamá-los de qualquer outra forma. Para mudar um 32
Teoria da Computação
pouco, vamos chamar os três estados desse novo autômato de X, Y e Z. Estes três estados estão representados abaixo, com balões indicando o signicado de cada um:
E agora, o que falta ao autômato? Você é capaz de completá-lo sozinho? O que falta são as transições entre os estados, que são as mudanças que podem ocorrer para cada símbolo lido. São elas que vão fazer com que, realmente, os estados tenham o signicado que planejamos. Se zermos essas ligações da forma errada, os estados não farão o que realmente queríamos. Ou seja, não basta pensar corretamente nas “situações” que os estados representam – é preciso criar transições que forcem os estados a cumprir o que deveriam. Voltando ao exemplo acima, vamos criar as transições em cada estado. Começando no estado inicial, o que deveria acontecer se fosse lido o símbolo 1? Bem, no estado X, ainda não foi lido nada, então, qualquer símbolo lido será o primeiro da cadeia. Bem, o estado que representa que “ o primeiro símbolo da cadeia é 1” é Y, então criamos uma seta de X para Y, anotada com o símbolo 1. E o que acontece em X se ler o símbolo 0? Essa situação seria a de que “o primeiro símbolo da cadeia é 0”. Porém, esta não é uma situação que interessa ao problema, pois, uma cadeia com essa característica deve ser rejeita! Para forçamos a rejeição da cadeia, vamos deixar o autômato sem essa transição. (Se você desejar fazer um autômato completo, crie um estado sumidouro e faça uma transição para ele, neste caso). Agora vamos tratar o estado Y. O que deveria acontecer se fosse lido um 1 nesse estado? Bem, considerando que Y representa a situação que “ o primeiro símbolo é 1”, ao ler mais um símbolo 1, o autômato poderá concluir que “os dois primeiros símbolos são 1”. 33
Teoria da Computação
Portanto, criaremos a transição de Y para Z, para o caso de ler 1. O caso de ler um símbolo 0 no estado Y é parecido com o do estado X: não desejamos que essa situação ocorra, então deixamos sem transição. Por m, vamos tratar o estado Z. O que deveria acontecer se fosse lido um símbolo 0? E se fosse lido um símbolo 1? Bem, como dissemos antes, o estado Z representa exatamente a situação que se deseja testar, que é se a cadeia começa com dois símbolos 1’s. Uma vez que já sabemos como a cadeia começa, você acha que algo que for lido depois vai ter importância? A resposta é não. Neste problema, só precisamos saber como a cadeia começa e se ela começar da maneira que esperamos, ela deve ser aceita, independente do que tem no restante da cadeia. Depois que sabemos que “os dois primeiros símbolos são 1”, podemos ler qualquer símbolo (0 ou 1) que essa situação da cadeia não se altera. Portanto, se o AFD ler 0 ou 1 no estado Z, ele deve permanecer nesse estado. O resultado nal será este AFD:
O autômato ainda está em sua representação gráca. A representação formal dele você pode obter seguindo tudo o que explicamos em seções passadas. Chamando este autômato de D 3, teríamos a seguinte representação formal: D3 = ({X, Y, Z}, {a, b}, δ, X, {Z}) δ
0
1
X
q1
q6
Y
q6
q6
Z
q3
q6
Resumindo o que vimos, para criar um AFD, você precisa começar analisando bem a linguagem (ou o problema). Em geral, a linguagem indicará uma propriedade desejada na cadeia. Então, na sua análise, você deve identicar as situações intermediárias em que o autômato pode se encontrar ao ler cadeia, até que ele identique a propriedade 34
Teoria da Computação
desejada. Cada situação dessas será um estado do autômato. Por m, você deverá criar as transições entre esses estados de uma maneira coerente com a situação que cada estado representa. Em certo sentido, criar AFDs para uma linguagem (ou problema) é como fazer um programa (em alguma linguagem de programação) para um dado problema. Em ambos os casos, você tem que fazer tudo com muita atenção, buscando colocar um sentido lógico no que está fazendo. Outra semelhança é que, tanto a programação como criação de AFDs, são coisas que se aprendem praticando. Por isso, vamos para mais uma seção de exercícios.
Aprenda Praticando
Desta vez, colocamos apenas exercícios resolvidos nesta seção, mas você terá a última seção inteira para resolver por conta própria. 1) Crie um AFD que represente o conjunto de cadeias sobre o alfabeto {0,1} que tenham “01” como subcadeia. Resposta: Em outras palavras, o objetivo do AFD que queremos
será encontrar, em qualquer parte da cadeia, um símbolo 0 seguido de um símbolo 1. Para denir os estados, temos que pensar nas possíveis situações que poderemos encontrar ao tentar atingir esse objetivo. A primeira situação, que representará o estado inicial, é a situação em que “nada foi visto” da sequência 01 que queremos. Voltaremos ao padrão que seguimos no início e chamaremos esse estado de q0 (mas podia ser qualquer outro nome). Uma segunda situação acontece depois de ler o símbolo 0, que o começo da sequência esperada. Essa situação seria “foi visto um 0 mas falta ver um 1” e será representada pelo estado q 1. Nesse estado, vale a pena dar mais uma dica: ao pensar nas situações relevantes para o problema, você pode pensar na parte que já foi lida da cadeia e na parte que falta ser lida. Claro que isso depende do problema e você precisará praticar para aprender a identicar as situações importantes (e que formarão os estados).
35
Teoria da Computação
Por m, a terceira situação é aquele em que, depois de ter visto um 0, também foi visto o símbolo 1. Essa é a situação que poderíamos descrever como “foi visto 01 (e não falta mais nada)” e ela será representa por q2. Como essa é a situação que queremos encontrar na cadeia, q 2 será um estado de aceitação. Um esboço do autômato, portanto, seria esse:
Já colocamos nele as transições mais importantes, que tratam o reconhecimento da sequência 01, pois isso pode ajudar a entender as situações que cada estado representa. Agora vamos completar as transições restantes. Vamos começar completando as transições de q 2, que são as mais fáceis de entender. Levando em consideração a descrição linguagem, perceba que basta identicar 01 em qualquer lugar da cadeia ela deverá ser aceita. Isso nos leva a aceitar que, uma vez que é atingido o estado de aceitação, não deveremos mais sair dele. Com isso, obtemos o seguinte:
Agora vamos ao estado q 0, que representa “nada foi visto”. Falta completar a transição para q0 lendo 1. Neste caso, não faria sentido ir para q1, pois ele representa o fato de que “foi visto um 0” e isso não é verdade agora. Na verdade, para a linguagem em questão, ver um 1 neste momento não tem importância alguma, ou seja, a cadeia não deve “caminhar” em direção à aceitação ainda. A cadeia também não deve ser rejeitada, porque falta procurar 01 no restante dela. Por isso, o correto a fazer é simplesmente permanecer em q 0, já que nada da cadeia 01 foi visto, mas algo ainda pode ser visto depois. E se estivermos em q 1 e lermos 0, o que deve acontecer? Este estado representa que foi visto um 0 e que falta ainda ver um 1. Ora, se vier outro 0, a situação ainda é a de que foi visto um zero, certo? Portanto, o autômato deve permanecer no próprio estado q1. 36
Teoria da Computação
Assim, chegamos ao autômato AFD nal:
E a sua representação formal será: D4 = ({q0, q1, q2}, {0, 1}, δ, q0, {q2}), onde δ é dado por: δ
0
1
q0
q1
q0
q1
q1
q2
q2
q2
q2
2) Crie um autômato para reconhecer a linguagem das cadeias sobre o alfabeto {0,1} que terminam em 11. Este problema tem alguma semelhança com o anterior, porque o objetivo também é identicar uma subcadeia. A diferença é simplesmente que, nesta linguagem, a subcadeia deve aparecer no m. Por isso, podemos começar este autômato de modo semelhante ao anterior. Neste AFD também teremos três estados, com sentidos parecidos aos dos estados que criamos na questão anterior: » q0 indicará que “nada da cadeia 11 foi visto (nos últimos símbolos)” » q1 indicará que “apenas um 1 foi visto (nos últimos símbolos) e falta ler outro 1” » q2 indicará que “dois 1’s foram vistos (nos últimos símbolos)” Assim, de modo semelhante à questão anterior, o desenho básico do autômato será este:
Analisemos, agora, a questão da cadeia 110, ou seja, se depois de ler 11 o próximo símbolo for 0. Neste caso, olhando para os 37
Teoria da Computação
últimos símbolos, não temos mais a subcadeia desejada. Por isso, temos que fazer o autômato voltar para o estado inicial, resultando nesse autômato:
Nosso autômato já computa corretamente cadeias como: 110, 1101, 11011, 11011011. Destas, apenas as duas últimas cadeias levam o autômato ao estado de aceitação, estando de acordo com a linguagem. Vejamos agora o caso de 111, 1111, 111111, pois todos eles devem ser aceitos. Isso quer dizer que quando o autômato está em q 2 e começa a ler uns, deverá continuar aceitando as cadeias. Isso faz sentido, pois q 2 representa a situação que os dois últimos símbolos são 1’s e, se outro símbolo 1 for lido, essa situação não mudou. Obtemos então o seguinte autômato:
Precisamos, ainda, denir o que fazer se q 1 ler 0. Este estado representa que “apenas um 1 foi visto”, então essa situação aconteceria com alguma trecho “10” de uma cadeia. Ora, esse trecho da cadeia representa algo de relevante para esta linguagem? Não! Então, devemos voltar para o estado q 0, que representa que nada do trecho 11 foi visto nos últimos símbolos. Em q0 o autômato esperará novamente que um trecho 11 apareça na cadeia. Por exemplo, essa “volta” a q 0 acontecerá com a cadeia 1011. Veja que, nela, o autômato, começando em q0, iria para q1, voltaria a q0 e, depois, seguiria para q1 e q2, terminando com a aceitação da cadeia. Por m, vamos denir a ação do autômato quando ele está em q0 e lê 0. Novamente temos um caso que não interessa 38
Teoria da Computação
para essa linguagem. Por isso, concluímos facilmente que o autômato deverá continuar em q 0. Temos agora o seguinte autômato:
Depois de concluir a construção da forma gráca do autômato, vale a pena fazer alguns testes com ele para ver se ele dá as respostas adequadas. Ou seja, devemos testar algumas cadeias que fazem parte da linguagem para ver se o autômato as aceita e devemos testar algumas cadeias que não fazem parte da linguagem para ver se ele as rejeita. Conrme que o autômato aceita todas essas cadeias (terminadas em 11): 011, 111, 01011, 0011, 000011. Conrme também que o autômato rejeita todas essas cadeias (que não terminam em 11): 01, 110, 101, 00, 0110, 01101. Este autômato está correto e vai dar as respostas esperadas, mas quando você for criar um autômato pode acontecer de ele não dar as respostas corretas ou aceitando ao invés de rejeitar ou rejeitando ao invés de aceitar. Nestes casos, reveja o processo de criação do AFD, usando a cadeia que deu a resposta errada. Isso pode lhe ajudar bastante a encontrar onde a sua lógica falhou! Voltando para o autômato, agora só precisamos escrevê-lo da maneira formal: D5 = ({q0, q1, q2}, {0, 1}, δ, q0, {q2}), onde δ é dado por: δ
0
1
q0
q0
q1
q1
q0
q2
q2
q0
q2
39
Teoria da Computação
3) O conjunto de cadeias sobre o alfabeto {0, 1} que não contenham 1’s. Observação: a cadeia vazia e deve ser aceita. Resposta: Essa questão nos faz pensar sobre duas
indagações: O autômato pode aceitar uma cadeia sem símbolos (a cadeia vazia)? Um estado pode ser inicial e de aceitação ao mesmo tempo?
A resolução dessa questão, apresentada a seguir, deve esclarecer essas perguntas para você. A linguagem pode ser entendida como o seguinte problema: testar se a cadeia não tem nenhum símbolo 1. Para isso, faremos um autômato que leia vários 0’s e aceite-os. Se algum 1 for lido, o autômato sai do estado de aceitação. Podemos começar este autômato com apenas um estado, que pode ser descrito como “só viu zeros” ou, ainda, “não viu símbolo 1”. Esta situação é a situação em que o autômato começa e, ao mesmo tempo, também é a situação desejado. Por isso, teremos de fazer o estado ser inicial e de aceitação ao mesmo tempo. O autômato parcial para o problema, portanto, seria esse:
Esse autômato já está correto. Ele aceita as cadeias corretas lendo-as completamente e parando no seu único estado, mas ele rejeita cadeias como 01 pelo simples fato de não haver transição para o símbolo 1. Se você desejar fazer um autômato completo, que sempre leia toda a cadeia, só precisa adicionar uma ação para o símbolo 1 que leve a um estado sugadouro, assim:
40
Teoria da Computação
Agora, daremos a denição formal desse autômato: D6 = ({q0, q1}, {0, 1}, δ, q0, {q0}), onde δ é dado por: δ
0
1
q0
q0
q1
q1
q1
q1
Pronto, terminamos esse exercício. Na parte nal deste capítulo, será a sua vez de praticar a criação de AFDs. Lembre-se de pedir ajudar no ambiente, se precisar!
Exercícios Propostos
1. Forneça AFDs que aceitem as seguintes linguagens sobre o alfabeto {0,1}: a) O conjunto das cadeias que terminam em 00. b) O conjunto das cadeias com três 0’s consecutivos (ou seja, cadeias com a subcadeia 000). c) O conjunto das cadeias que têm 011 como subcadeia. d) O conjunto das cadeias com número par de 1’s. e) O conjunto das cadeias com número ímpar de 0’s. f) O conjunto das cadeias tais que cada bloco de cinco símbolos consecutivos contém, pelo menos, dois zeros. g) O conjunto de cadeias cujo terceiro símbolo a partir da extremidade direita é 1. h) O conjunto de cadeias que começam ou terminam (ou ambos) com 01. i) O conjunto de cadeias tais que o número de 0’s é divisível por 5. j) O conjunto de cadeias tais que o número de 1’s é divisível por 3.
41
Teoria da Computação
Capítulo 3 – Autômatos Finitos Não-Determinísticos Você estudou, no capítulo anterior, os Autômatos Finitos Determinísticos (AFD), que formam o modelo computacional mais simples do curso. Você conhecerá agora um modelo similar de autômatos que, em certo sentido, oferece mais liberdade a você, criador de autômatos. A ideia deste novo modelo é de um autômato que pode estar em vários estados ao mesmo tempo ou ainda que não precise ler um caractere para mudar de estado. Estas são as duas principais características de um autômato nito não-determinístico (AFND). Em outras palavras um autômato lendo um símbolo pode ir para um estado ou para vários estados ao mesmo tempo. Também pode estar em um estado e passar para outro sem necessariamente ler um símbolo. Estudaremos as características dos AFND e, posteriormente, compararemos com os AFD para vericar se as diferenças do AFND o tornam mais poderoso.
3.1 Explicação Inicial
Vejamos agora a forma gráca de um AFND:
A única diferença desse autômato para o que estamos acostumados a trabalhar é que q0 tem duas transições lendo 1. Este estado, quando ler 1, vai para o conjunto {q0, q1} e ca nos dois estados ao mesmo tempo. Veremos agora o comportamento do autômato com algumas entradas: suponha a cadeia 110, começamos em q 0 lendo 1 e indo para os estados {q0, q1}. O diagrama a seguir mostra o resultado da ação:
42
Teoria da Computação
O autômato está, ao mesmo tempo, no estado q 0 e q 1. A partir de agora, toda vez que o autômato ler um símbolo de entrada, deve-se acompanhar o andamento de todos os estados atuais. Lendo o segundo símbolo, temos:
O estado q1 não tem transição lendo 1 então simplesmente o caminho de q1 é abandonado. Note também o estado q 0 que, lendo 1, novamente bifurcou e estamos novamente em dois estados.
Vamos então para o terceiro símbolo da cadeia. Neste caso, temos duas situações: calculando primeiro q 0, lendo 0, obtemos q0. Agora, a partir de q1, lendo 0, obtemos q2. O resultado é esse:
Também poderíamos representar esse processo de teste da cadeia mostrando os conjuntos obtidos a cada etapa, assim: 43
Teoria da Computação
{q0} → lendo 1 → {q0, q1} → lendo 1 → {q 0, q1} → lendo 0 → {q0, q2} Igualmente ao estudado em AFD, quando terminamos de ler a cadeia e estamos num estado de aceitação, aceitamos esta cadeia no AFND. Em particular, se o AFND terminar em vários diferentes estados, basta ter um estado de aceitação, para que a cadeia seja considerada aceita. No exemplo acima, o AFND terminou no conjunto de estados {q0, q2}. Como um deles (q2) é estado de aceitação, a cadeia 110 foi aceita. Isso signica que nem sempre você vai precisar calcular todos os caminhos possíveis no AFND para uma certa cadeia. Bastará que você ache um caminho que chegue ao estado de aceitação e você poderá concluir imediatamente que a cadeia é aceita, mesmo sem você ter calculado os outros caminhos! No exemplo anterior, bastaríamos ter descoberto o caminho abaixo para concluir que a cadeia 110 é aceita pelo AFND:
Como este caminho sozinho, termina no estado de aceitação, não precisamos examinar os outros caminhos – concluímos que a cadeia é aceita. Um detalhe que gostaríamos de lembrar é que, assim como os AFDs, os AFNDs servem para representar linguagens. Eles fazem isso de uma maneira sutil, tratando a linguagem como o problema de “aceitar as cadeias que são da linguagem e rejeitar as que não são da linguagem”. Isso precisa estar sempre claro na sua mente! Porém, queremos representar as linguagens da maneira mais matematicamente precisa que pudermos. Por isso, vamos agora denir os AFNDs formalmente, como zemos para os AFDs.
44
Teoria da Computação
3.2 Definição Formal
Parecido com os AFDs, podemos denir matematicamente um autômato nito não-determinístico N por meio de uma quíntupla (ou 5-tupla): N = (Q, ∑, δ , s, F) onde: » Q é o conjunto nito de estados. » ∑ é o alfabeto de entrada. »
é a função de transição. É do tipo: Q x ∑ → 2Q. Signica dizer que estando em um estado e lendo um símbolo do alfabeto faz o autômato passar para um conjunto de estados do autômato. δ
» s ∈ Q é o estado inicial. » F ⊆ Q: os elementos de F são os estados nais ou estados de aceitação. Você já deve ter percebido que Q, δ, q0 e F têm o mesmo signicado para os AFD. A única diferença é no δ, que agora pode acessar zero, um ou mais estados. Como exemplo, vamos fazer a representação formal do autômato N1, que é levemente diferente do AFND mostrado na seção anterior. N1 é mostrado abaixo em sua forma gráca.
Formalmente, este autômato pode ser representado assim: N1 = ({q0, q1, q2}, {0, 1}, δ, q0, {q2}) Com o seu conhecimento de AFD deve ser fácil compreender a parte acima. Porém, ela está incompleta porque ainda falta denir a função δ – e esta é a parte do AFND que realmente é diferente de um AFD. Porém, da mesma maneira que nos AFDs, podemos representar essa função com uma tabela. A diferença é que a célula da tabela mostrará um conjunto de destinos possíveis ao ler o símbolo dado. No caso do autômato N 1, a função de transição tabela:
45
δ
é dada pela
Teoria da Computação
δ
0
1
q0
{q0}
{q0, q1}
q1
{q2}
Ø
q2
Ø
Ø
3.3 Relação entre AFD e AFND
Pelo que mostramos até agora, talvez que em você a sensação de que autômatos nitos não-determinísticos (AFNDs) são mais poderosos que autômatos nitos determinísticos (AFDs), pois AFNDs podem estar em mais de um estado ao mesmo tempo. Na verdade, dizer que um modelo é mais poderoso que outro signica armar a existência de alguma linguagem que os AFND reconhecem e que nenhum AFD pode reconhecer. O objetivo desta seção é mostrar que isso não é verdade. Queremos mostrar a equivalência entre os dois modelos. Para isso, podemos mostrar como converter de um modelo para outro, nas duas direções. Primeiro, teríamos que mostrar como converter de um AFD para um AFND, mas essa é uma conversão trivial. Na forma gráca, nem mesmo seria necessário alterar nada no AFD para convertê-lo para um AFND, pois, tudo o que um AFD permite, um AFND também permite. A conversão de um AFD para um AFND provaria uma pequena diferença apenas na representação formal da função de transição: seria necessário transformar cada célula da tabela do AFD, que lista um certo estado, em um conjunto unitário contendo esse mesmo estado. O mais difícil é mostrar a outra conversão: de um AFND para um AFD. A partir de agora, vamos mostrar o procedimento para você transformar qualquer AFND em um AFD equivalente, de maneira que ambos reconheçam a mesma linguagem (ou seja, toda cadeia que um deles aceitar, o outro também deve aceitar; toda cadeia que um deles rejeitar, o outro também deve rejeitar). Tome como exemplo o seguinte AFND:
46
Teoria da Computação
N2 = ({q0, q1, q2}, {0, 1}, δ, q0, {q2}) δ
0
1
q0
{q0, q1}
{q0}
q1
Ø
{q2}
q2
Ø
Ø
Note que este autômato apresenta transições que levam para um conjunto contendo zero, um ou mais estados, que são as características que distinguem os AFNDs. Para convertermos esse AFND (ou outro qualquer), a grande pergunta que precisaremos responder será: como fazer os conjuntos de estados, presentes na tabela do AFND, aparecerem como um único estado no AFD? Por exemplo, no AFND acima, se estiver em q 0 e ler 0, ele vai para dois estados: q0 e q1. Em um AFD, ele teria que ir para um só estado, mas qual? Bem, a ideia da conversão de AFND para AFD será usar um conjunto de estados do AFND como sendo um único estado para o AFD. Por exemplo, {q0, q1} são dois estados do AFND, mas será
tratado como sendo um só estado para o AFD. Sim, sabemos que isso confunde um pouco no início, mas pense como se {q 0, q 1} fosse apenas um nome de um estado. Um nome estranho, em relação aos que vínhamos usando, mas lembre-se de que os nomes dos estados não fazem diferença nos autômatos. No AFD, um estado com nome {q 0, q1} representaria a situação em que o AFND estaria, ao mesmo, nesses dois estados (e não estaria em nenhum outro mais). Então, com esse artifício, conseguimos responder a “grande pergunta” levantada antes sobre como trocar um conjunto de estados por estados individuais. Se você entendeu esse ponto da explicação, o restante será mais simples. Se não entendeu, releia ou busque ajuda no ambiente, que existe exatamente para ajudar você! Como ponto de início da conversão para um AFD, precisamos denir qual será o estado inicial. Bem, veja que o AFND começa 47
Teoria da Computação
em um estado só. O seu início nunca começa “bifurcado” em várias opções. Por isso, o estado inicial do AFD será um conjunto que tem apenas o estado inicial do AFND. No caso do autômato N 2, ele será o estado {q0}. A partir desse estado, construiremos as transições e “descobriremos” outros estados importantes para o novo autômato. Por enquanto a tabela com as transições do AFD seria apenas essa: δ
0
1
{q0}
Bem, para completarmos as transições nessa tabela, bastará olhar as transições de q 0 em N2. Esse é um caso simples, porque o estado {q0} tem apenas um estado do AFND. O resultado, por enquanto, seria essa tabela: δ
0
1
{q0}
{q0, q1}
{q0}
Agora, veja que, nas células da tabela, apareceu o conjunto {q0, q1}. Este conjunto representa um novo estado e deverá ser representado por uma nova linha da tabela: δ
0
1
{q0}
{q0, q1}
{q0}
{q0, q1}
Agora, devemos completar as transições para {q0, q1}, que não é um caso tão simples quanto o primeiro porque agora temos dois estados. Você vai se questionar: estando no estado {q 0, q1}, que representa q0 e q 1 ao mesmo tempo, e lendo o caractere 0, quais os caminhos seguidos pelo autômato? A partir de q 0, lendo 0, acessamos o conjunto de estados {q 0, q1} e a partir de q1 acessamos Ø, então tomamos a união desses dois resultados {q 0, q1} υ Ø = {q0, q1}. Com isso, preenchemos a primeira célula vazia: δ
0
1
{q0}
{q0, q1}
{q0}
{q0, q1}
{q0, q1}
48
Teoria da Computação
Para preencher a próxima célula, devemos analisar o que acontece estando em {q 0, q1} e lendo 1. Por meio de q0, acessamos {q0} e, por meio de q1, acessamos {q2}. Logo, o resultado será a união {q0} υ {q2} = {q0, q2}. Temos então a nova linha preenchida: δ
0
1
{q0}
{q0, q1}
{q0}
{q0, q1}
{q0, q1}
{q0, q2}
Essa será a regra geral para descobrir as transições de um conjunto de estados {e1, e2, ..., en}: para cada símbolo x, fazemos a transição de e1, depois de e2, depois de ..., depois de en. Por m, fazemos a união entre todos os conjuntos resultantes. Essa união será, então, tratada como um novo estado do AFD que estamos criando. Bem, continuando a explicação da conversão do autômato N 2, veja que aparece um novo conjunto na tabela: {q0, q2}. Precisamos saber o comportamento do autômato neste estado, então criamos uma nova linha para ele: δ
0
1
{q0}
{q0, q1}
{q0}
{q0, q1}
{q0, q1}
{q0, q2}
{q0, q2}
Vamos agora descobrir as transições para {q 0, q2}. Em q0, lendo 0, o autômato vai para {q0, q1} e q2 lendo o mesmo símbolo vai para Ø. O resultado será {q0, q1} υ Ø = {q0, q1}. Agora, q0 lendo 1 continua em q0, enquanto q2 lendo 1 vai para Ø, logo obtemos o resultado {q0} υ Ø = {q0}. A tabela, portanto, ca assim: δ
0
1
{q0}
{q0, q1}
{q0}
{q0, q1}
{q0, q1}
{q0, q2}
{q0, q2}
{q0, q1}
{q0}
E agora, o que fazer? Surgiu algum estado (conjunto) novo? A resposta é: não. Então, a construção da tabela se encerrou. O que 49
Teoria da Computação
falta agora é denir quem são os estados de aceitação. Para isso, olhe os estados de aceitação do AFND. Veja que, no caso do exemplo N2, tem apenas um estado de aceitação: q 2. Lembre-se também que, se um AFND termina em um conjunto de estados, basta um deles ser estado de aceitação para aceitar uma cadeia. Por isso, na conversão, bastará um conjunto conter q 2 para que ele seja considerando um estado de aceitação. No caso, o único estado de aceitação do exemplo é {q0, q2}. Para simplicar as respostas dos exercícios, você não precisará dar o restante da representação formal do AFD que acabou de construir. Bastará adicionar duas informações na tabela: qual é o estado inicial e quais são os estados de aceitação. Para indicar qual é o estado inicial, coloque uma seta na linha que o representa (que deverá ser a primeira linha da tabela, se você fez da maneira que ensinamos aqui). Já nos estados de aceitação, coloque com um asterisco ao lado dele na tabela. A tabela resultante do AFD seria essa: δ
0
1
→ {q0}
{q0, q1}
{q0}
{q0, q1}
{q0, q1}
{q0, q2}
* {q0, q2}
{q0, q1}
{q0}
Como a representação gráca de um autômato costuma ser mais agradável e fácil de entender, você também deverá construí-la. Isso pode ser feito com facilidade a partir da tabela das transições. Você só precisará lembrar daquele detalhe importantíssimo dito no começo: que os conjuntos serão apenas nomes de estados individuais. No caso da tabela acima, o resultado seria essa representação gráca do AFD:
Você, agora, pode fazer alguns testes e vericar que este autômato 50
Teoria da Computação
é realmente equivalente ao autômato N 2 que mostramos antes. Teste e conrme que ambos aceitam as cadeias: 01, 001, 0101, etc. Também examine ambos para conrmar que eles rejeitam as cadeias: e, 1, 00, 110, 010, 011, etc. O fato de que ambos aceitam as mesmas cadeias e rejeitam as mesmas cadeias faz com que sejam equivalentes. Porém, perceba a diferença entre eles: no AFND, você não tem um caminho bem claro, bem determinado a ser seguido – você terá que testar vários caminhos. Por isso, esse autômato recebe esse nome de “não-determinístico”: porque ele pode escolher entre várias ações num mesmo instante. Já no AFD, o caminho a ser seguido será sempre bem denido. Ele vai seguir sempre para um estado por vez e vai parar em um só estado – tudo muito bem determinado. Por isso, esse autômato recebeu o nome de “determinístico”: porque ele só vai ter uma ação para escolher a cada instante. Qual a vantagem então de se trabalhar com AFNDs? Em alguns casos, será mais fácil construir um AFND do que um AFD. Pode acontecer do AFND ter menos transições e menos estados do que o AFD equivalente, facilitando a vida de quem o constrói. Porém, depois de construído o AFND, vale a pena convertê-lo para um AFD usando a técnica que mostramos aqui. Para encerrar, lembramos a você, leitor, que essa seção mostrou que (mesmo funcionando de maneiras diferentes) AFD e AFND são dois modelos equivalentes, ou seja, toda linguagem que um deles pode representar o outro também pode representar. Isso foi provado ao mostrarmos como converter um autômato de um tipo para o outro tipo. Na seção a seguir, veremos que existe um terceiro tipo de autômato, que também é equivalente aos dois que já vimos.
3.4 Um novo tipo de transição (ε)
E se pudéssemos mudar de estado sem precisar ler nenhum símbolo? Você não leu errado, há uma maneira de, no momento que o autômato chega em um estado, ele passar imediatamente para outros estados sem ter lido mais nenhum símbolo. Podemos fazer este tipo de transição com o uso de um identicador especial: a letra grega e (épsilon). Um autômato com essa propriedade é chamado de AFND com e-transições ou e-AFND.
51
Teoria da Computação
Atenção Lembre-se que já usamos ε para representar a cadeia vazia, ou seja, a cadeia sem símbolos. Agora, vamos usá-lo para representar uma transição que não lê símbolo. Em todo caso, ε está representando a ausência total de símbolos.
Essas transições serão transições espontâneas no autômato, que o autômato pode fazer ou não, independente da cadeia que ele recebeu de entrada. Vamos analisar um e-AFND e entender seu funcionamento. Suponha um autômato que reconhece a soma de um inteiro positivo ou negativo com um decimal positivo.
Vamos acompanhar agora o funcionamento deste autômato com a expressão “-12+2.6”. Começamos com estado q 0: o número pode ser iniciado por +, -, ou nenhum caractere.
Note que, inicialmente, o autômato já está nos estados q 0 e q1 ao mesmo tempo por causa da transição e entre esses estados. A transição espontânea faz com que o autômato avance nas transições mesmo sem ter lido qualquer caractere da cadeia. Começamos a ler então o primeiro símbolo (-). Seguindo o caminho de q 0 lendo menos (-) faz o autômato passar para o estado q 1, mas de q1, que é o segundo caminho, não há transição então este percurso é abandonado. Os próximos são lidos e o autômato se comporta como um simples AFD lendo 12+ e obtemos o seguinte resultado:
52
Teoria da Computação
Estando em q3 o autômato lê o próximo caractere dois (2) e imediatamente o autômato vai para o estado q 4 mas tratamos aqui de um autômato com e transições. O e mais à direita indica que chegando ao estado q4 imediatamente o autômato passa também para o estado q6, e por se tratar de um estado de aceitação se a cadeia terminasse neste momento seria aceita. Vejamos como ca a descrição dos caminhos do autômato até o momento:
Lembre-se que o autômato está no estado q 4 e q6. Lendo o próximo caractere (.), o caminho de q 6 é abandonado, enquanto, no outro caminho, seguimos de q 4 para o estado q5, como é mostrado a seguir:
53
Teoria da Computação
Nosso próximo passo é acompanhar o ponto (.) que faz o autômato paralisar o caminho atual de q 6 e ir para q5 pelo caminho de q4. Em seguida o autômato lê o seis (6) e vai para q 6, e como é estado de aceitação e terminamos de ler a cadeia aceitamos a cadeia. Tente repetir o funcionamento do autômato com esse número e crie novos números para observar o comportamento do autômato com o uso das transições e. Um detalhe importante do modelo e-AFND é que ele é equivalente tanto ao modelo AFD quanto ao modelo AFND (sem transição e). Porém, desta vez, não mostraremos como poderíamos converter de um modelo para outro. Apenas saiba que é possível converter. Mais informações você pode encontrar na bibliograa.
Aprenda Praticando
Nesta seção, mostraremos passo-a-passo como resolver, na prática, os problemas aprendidos nas seções anteriores. Atente aos exercícios e se possível resolva-os novamente sem a ajuda da apostila. É essencial que você só avance aos exercícios seguintes quando entender os resolvidos. Lembre-se que você tem ajuda dos seus colegas de turma, tutores, professores, livros entre outros para esclarecer suas dúvidas. 1. Crie o autômato nito não-determinístico que reconheça a
seguinte linguagem: “o conjunto de todas as cadeias sobre {0,1} que tenham 01 como subcadeia”. 54
Teoria da Computação
Resposta: Primeiro, temos que criar um autômato que
reconheça a cadeia 01. Obtido facilmente e é mostrado abaixo:
Só precisamos vericar as condições: a subcadeia pode estar após a leitura de alguns caracteres, logo precisamos adicionar transições iniciais. Também 01 pode estar antes do nal da cadeia por isso precisamos adicionar transições no nal. Uma vez lido 01 o autômato não sai mais do estado de aceitação. Temos como resultado:
Agora só precisamos descrevê-lo de maneira formal: M = ({q0, q1, q2}, {0,1}, δ, {q2}), onde: δ
0
1
→ q0
{q0,q1}
{q0}
q1
Ø
{q2}
* q2
{q2}
{q2}
2. Mostre os conjuntos de estados pelos quais o autômato passa
quando recebe a cadeia 10010. No nal, diga se a cadeia é aceita ou não e justique.
Resposta: Começamos pelo estado q 0. Depois de ler o símbolo 55
Teoria da Computação
1, o autômato vai permanecer em q 0. Agora, em q0, quando lê o próximo símbolo, que é 0, o autômato pode ir para dois estados: {q1, q2}. Podemos representar esse início assim: {q0} → lê 1 → {q0} → lê 0 → {q1, q2} → ... Agora o autômato está em dois estados, então deveremos acompanhar o que acontece nos dois caminhos ao ler o próximo símbolo da cadeia, que é 0. Se estiver em q1 e ler um 0, vai {q2}; se estiver em q2, ao ler o 0, não tem opção, ou seja dá o conjunto vazio. O resultado é que o autômato poderá estar apenas no conjunto {q 2}. {q0} → lê 1 → {q0} → lê 0 → {q1, q2} → lê 0 → {q2} → ... Ainda falta ler o trecho 10 da cadeia. Ao ler 1 em q 2, o resultado é {q0}, completando mais um passo. Agora, lendo o último símbolo, que é 0, a partir de q 0, o resultado é {q1, q2}, como vimos antes. Assim, os conjuntos obtidos ao ler cada símbolo da cadeia 10010 serão: {q0} → lê 1 → {q0} → lê 0 → {q1, q2} → lê 0 → {q2} → lê 1 → {q0} → lê 0 → {q0, q2} Note que ao ler 10010, terminamos nos estados q 0 e q2 ao mesmo tempo. A cadeia será aceita e a justicativa para isso é que terminamos de ler a cadeia e um dos estados atingidos é um estado de aceitação (o estado q 2).
Atividades e Orientações de Estudo
Você encontrará nesta seção uma relação de exercícios importantes. É interessante que você se esforce ao máximo para tentar resolver e entender cada questão desta. Resolvê-los pode ser comparado a um exercício de matemática ou de programação: não existe um método pré-denido cada um pode ver o problema de uma maneira e obter respostas diferentes. É importante comparar com as respostas de outros colegas e tentar encontrar falhas nos autômatos uns dos outros. É um ótimo exercício para exercitar a mente na resolução de problemas. Nunca esqueça do apoio dos tutores no seu aprendizado, pois 56
Teoria da Computação
eles estão sempre prontos para tirar suas dúvidas e lhe orientar nos exercícios. 1. Construa os AFND que reconheçam as seguintes linguagens sobre o alfabeto {0,1}: a) O conjunto de cadeias que começam por 0 e terminam com 1. b) O conjunto de cadeias que terminam com 00 c) O conjunto de cadeias que têm 01 como subcadeia. 2. Mostre um AFND que aceita o conjunto de cadeias sobre o alfabeto {0, 1, ..., 9} tal que o dígito nal já tenha aparecido antes na palavra. 3. Mostre um AFND que aceita o conjunto de cadeias sobre o alfabeto {0, 1, ..., 9} tal que o dígito nal não tenha aparecido antes na palavra. 4. Forneça um AFND que aceite o conjunto de cadeias sobre o alfabeto {0, 1} tais que não existam dois 0’s separados por mais de três 1’s. 5. Converta o autômato não-determinístico N1 visto antes para um autômato determinístico equivalente.
Saiba Mais
Muitas vezes precisamos estudar um mesmo assunto por diversos ângulos e de maneiras diferentes. Citamos abaixo alguns caminhos para você estudar o assunto com linguagens e notações diferentes ou até mesmo com a mesma. Sua pesquisa é importante principalmente se você não está completamente habituado com o assunto. Internet: http://www.ic.uff.br/%7Ecbraga/tc/2005.1
- Curso de teoria da
computação. 57
Teoria da Computação
http://www.inf.puc-rio.br/%7Einf1626
- Notas de aula de teoria da
computação. http://www.deamo.prof.ufu.br/CursoLFA.html
- Curso com vários
exercícios resolvidos. http://homepages.dcc.ufmg.br/~nvieira/index.html -
Notas de teoria de
vários anos.
Resumo
Vimos neste volume nosso primeiro modelo matemático de máquinas. Os autômatos nitos são simples e por isso não apresentam um grande poder computacional comparando com os modelos que veremos adiante. O principal modelo para descrever um autômato nito é como uma função: A = (Q, ∑, δ, s0, F), onde: » Q é o conjunto nito de estados. » ∑ é o alfabeto de entrada. »
δ
é a função de transição.
» s0 ∈ Q é o estado inicial. » F ⊆ Q; os elementos de F são os estados nais ou estados de aceitação. Existem dois tipos de autômatos: AFD e AFND: Os primeiros só podem estar em um estado ao mesmo tempo e normalmente todas as suas transições são preenchidas. Um AFND pode estar em mais de um estado ao mesmo tempo ou mesmo em transição para nenhum estado. Embora os AFND’s tenham essa peculiaridade ambos modelos apresentam o mesmo poder computacional, ou seja, o problema que pode ser resolvido por um AFND pode ser também por um AFD e vice-versa.
58
Teoria da Computação
Referências
Leitura Adicional:
HOPCROFT, John E.; MOTwANI, Rajeev.; ULLMAN, Jeffrey D. Introdução
à
Teoria
de
Autômatos,
Linguagens
e
Computação. Editora Campus, 2002.
LEwIS, Harry R.; PAPADIMITRIOU, Christos H. Elementos de Teoria da Computação, Bookman, 2ª edição. MENEZES, Paulo Blauth. Linguagens Formais e Autômatos. Editora Sagra Luzzatto, 2000. SIPSER, Michael. Introdução a Teoria da Computação. Editora Thomson Pioneira, 2007.
59