Conceitos Básicos Vocabulário Cadeias Linguagens Expressões Regulares Problema X Linguagem Alfabeto ou Vocabulário: Conjunto finito não vazio de símbolos. Símbolo é um elemento qualquer de um alfabeto. Ex: {A,B,C,….Z} alfabeto latino (maiúsculas) {, , , , , …, } alfabeto grego {0,1} alfabeto binário {0,1,2,3,4,5,6,7,8,9} alfabeto de dígitos {a, b} Cadeia ou palavra: Concatenação de símbolos de um alfabeto. Define-se como cadeia vazia ou nula uma cadeia que não contém nenhum símbolo. Ex: aab 123094 l - cadeia nula Comprimento de cadeia: Número de símbolos de uma cadeia. Ex: |aab| = 3 |123094|=6 |l | =0 Concatenação de cadeias: Define-se a concatenação z de uma cadeia x com uma cadeia y como sendo a justaposição dos símbolos de ambas as cadeias, formando a cadeia xy. |z| = |x| + |y| Ex: x = abaa; y = ba z = abaaba |z|=4+2=6 x = ba; y = l z = ba |z|=2+0=2 Produto de alfabetos: É o produto cartesiano de alfabetos. Ex: V1 = {a,b}; V2 = {1, 2, 3} V1.V2 = V1xV2 = {a1, a2, a3, b1, b2, b3} Observe que V1.V2 V2.V1 Exponenciação de alfabetos: São todas as cadeias de comprimento n sobre V (Vn). V0={l}, V1=V, Vn=Vn-1.V Ex: V = {0, 1} V3 = V2.V = (V.V).V = {00, 01, 10, 11}.{0,1} = {000, 001, 010, 011, 100, 101, 110, 111} |V|=8 |Vn| = mn onde |V| = m Fechamento (Clausura) de um Alfabeto: Seja A um alfabeto, então o fechamento de A é definido como A* = A0 A1 A2 ... An ... Portanto A* = conjunto das cadeias de qualquer comprimento sobre o alfabeto A. Ex: A = {1} A* = {l, 1, 11, 111, ...} Fechamento Positivo de A: A+ = A* - {l} (todas as cadeias não vazias sobre o alfabeto A) Exemplo: Se T = {a, b} e NT = {A, B, C} Então (T U NT) * = {a, b, A, B, C}* = {l} U {a, b, A, B, C} U {a, b, A, B, C}2 U {a, b, A, B, C} 3 U …. = {l, a, b, A, B, C, aa, ab, ba, bb, aA, aB, aC, bA, bB, bC, Aa, Ab, Ba, Bb, Ca, Cb, AA, AB, AC, BA, BB, BC, CA, CB, CC, aaa, aab, aba, abb, baa, bab, bba, bbb, aaA, aaB, aaC, …, Aaa, …, ABC, …, CCC, … Ou seja, (T U NT) * consiste de todas as cadeias, de qualquer comprimento, de símbolos de T e/ou NT. Linguagem é um conjunto de cadeias de símbolos sobre um alfabeto/vocabulário, V. É um subconjunto específico de V*. Estas cadeias são denominadas sentenças da linguagem, e são formadas pela justaposição de elementos individuais, os símbolos da linguagem. Ex: e bc) V = {a, b, c} L = {ab, bc} ( linguagem formada pelas cadeias ab L V2 Formadores de Conjuntos Expressões que ajudam a descrever uma linguagem (em geral infinita): L = { w | algo sobre w } O conjunto de cadeias w tais que …(o que for dito sobre w à direita) Exs.: 1. L={ w | w consiste de um número igual de 0’s e 1’s } 2. L={ w | w é um número inteiro binário primo } 3. L={ w | w é um programa em C sintaticamente correto } 4. L={ 0n 1n | n 1} = {01, 0011, 000111, ...} 5. L={0i 1j | 0 i j} = {l, 01, 1, 11, 111..1, 011, 0011, 00111, ...} 6. L={abn anb| n>=0} (linguagem formada por todas as cadeias que começam com "a" seguido de um número qualquer de "b"'s OU começam com um número qualquer de "a"'s seguidos de um "b", por exemplo a, b, ab, abb, aab, aaab, ...). Também se escreve abn + anb Expressões Regulares (ER) Uma ER sobre um alfabeto é definida como: a) é uma ER e denota a linguagem vazia b) l é uma ER e denota a linguagem contendo a palavra vazia, ie {l} c) Qualquer símbolo x é uma ER e denota a linguagem {x} d) Se r e s são ER denotando as linguagens R e S então: • • • (r+s) ou (r|s) é ER e denota a linguagem R S (rs) é ER e denota a linguagem RxS = {w=uv | u R e v S} (r*) é ER e denota a linguagem R* 9 Exemplos • 00 é uma ER denotando a linguagem {00} • (0+1)* denota a linguagem formada por todas as cadeias de 0´s e 1´s • (0+1)* 00 (0+1)* denota todas as cadeias de 0´s e 1´s com ao menos dois 0´s consecutivos • a+b*c denota um único a ou zero ou mais vezes b seguido de c 10 • (0+1)* 001 denota todas as cadeias de 0´s e 1´s terminadas em 001 001, 000001, 101001, 111001, ... • 0*1*2* denota qualquer número de 0´s seguido por qualquer número de 1´s seguido por qualquer número de 2´s l, 0, 1, 2, 12, 001, 1112, 001122,... • 01* + 10* denota a linguagem consistindo de todas as cadeias que são um único 0 seguido por qualquer número de 1´s OU um único 1 seguido por qualquer número de 0´s. 0, 01, 01111, 1, 10, 100000, ... 11 Omissão de parênteses • Para omitir parênteses devemos respeitar: – O fecho (*) tem prioridade sobre a concatenação (rs), que tem prioridade sobre a união (r+s). – A concatenação e a união são associadas da esquerda para a direita. – Ex: 01* + 1 é agrupado como (0(1*)) + 1 => L = {1, 0, 01, 011,...} • Usamos parênteses quando queremos alterar a prioridade: • (01)* + 1 => L = {1 U (01)n | n >= 0} = {1, l , 01, 0101,...} • 0(1* + 1) => L = {w {0,1}* | w começa com 0 seguido de 1n | n>=0} Lei distributiva à esq = 01* + 01 = {0,01,011,0111,...} 12 Escreva a ER equivalente a: • O conjunto de cadeias sobre {0,1} que termine com três 1´s consecutivos. (0+1)*111 • O conjunto de cadeias sobre {0,1} que tenha ao menos um 1. (0+1)*1(0+1)* • O conjunto de cadeias sobre {0,1} que tenha no máximo um 1. 0*(1+l)0* 13 LINGUAGEM OU PROBLEMA?? • A Tese de Church-Turing diz que o conjunto de problemas que podem ser resolvidos por Máquinas de Turing (MT) coincide com o conjunto de problemas que podem ser resolvidos pelos computadores. • Acontece que, como veremos mais tarde, o que as MT fazem é reconhecer Linguagens. • Como, então, relacionamos Linguagens 14 e Problemas? LINGUAGEM OU PROBLEMA?? Quando escolhemos a Máquina de Turing como modelo de computabilidade, definimos então um Problema como equivalente a decidir se uma dada cadeia pertence à uma linguagem específica: o conjunto de cadeias que são soluções do problema. Assim, um problema é sinônimo de decidir a pertinência de uma cadeia a um conjunto (uma linguagem). Mais precisamente: Se é um alfabeto e L é uma linguagem sobre , então o problema L é: Dada uma cadeia w em *, decidir se w está ou não em L. (Questão de Decisão) LINGUAGEM OU PROBLEMA?? Ex.: O problema de testar se um número binário é um número primo pode ser expresso pela linguagem Lp que consiste em todas as cadeias binárias cujo valor como número binário é primo. Ou seja, dada uma cadeia de 0’s e 1’s, diremos sim se a cadeia for a representação binária de um primo, ou diremos não, caso contrário. O conjunto das cadeias que satisfazem a condição de número binário primo representa a linguagem-solução do problema. Assim, se queremos estudar a eficiência da solução desse problema, basta estudarmos a eficiência de se verificar se uma dada cadeia candidata pertence ou não à Linguagem Lp. Linguagem ou Problema? • São a mesma coisa. O termo usado depende do ponto de vista. • Encarar problemas como uma questão de pertinência de conjunto (pertence ou não à linguagem) tem sido útil aos estudos da teoria da complexidade. • Nessa teoria, estamos interessados em provar limites inferiores sobre a complexidade de certos problemas (p.ex. provar que eles não podem ser resolvidos em um período de tempo menor que o exponencial no tamanho de sua entrada). • Acontece que a versão baseada em linguagens (de decisão sobre pertinência) de problemas conhecidos são tão difíceis quanto suas versões do tipo “resolva isso”. • Ao reduzirmos um problema para sua versão de pertinência e provarmos que é difícil resolvê-la, então podemos concluir que resolver o problema inicial será igualmente difícil. (Técnica de Prova por Redução) P i Lp ? No entanto... Nem sempre os problemas reais são questões de decisão. São em geral solicitações para calcular ou transformar alguma entrada. Ex. A tarefa de um analisador sintático (parser) de um compilador pode ser vista como um problema de decisão: a cadeia de entrada (programa escrito numa linguagem de programação X) é uma sentença da linguagem Lx, ou seja, pertence ao conjunto dos programas válidos em X? E a tarefa do compilador? O compilador como um todo transforma um programa escrito em código fonte em outro, em código objeto. Essa tarefa está longe de responder simplesmente sim ou não sobre a validade de um programa. Exemplo • Seja P1 o problema de compilar um programa na linguagem de programação X. Quão difícil é resolvê-lo? (qual sua complexidade?). • Considere agora o problema de decisão P2: dada uma cadeia, ela pertence à linguagem de cadeias válidas na linguagem de programação X - Lx? Ou seja, P2 corresponde ao parser dos programas em X. • Repare que P2 é parte da solução de P1. • Se provarmos que é difícil resolver P2, então P1 não pode ser mais fácil. Isto é, se é difícil a tarefa do parser, a do compilador não pode ser mais fácil. • Por contradição: se fosse fácil (eficiente) gerar código-objeto, poderíamos usar o próprio compilador para, ao ter sucesso ao produzir o código para um programa (cadeia), concluir que essa entrada se trata de um elemento válido de Lx. Assim, contradizemos a suposição de que testar a pertinência a Lx é difícil. Assim, temos uma prova por contradição da afirmação: Se o teste de pertinência a Lx é difícil, então compilar programas na linguagem de programação X é difícil. Essa técnica de redução é extremamente útil no estudo da complexidade de problemas, e é facilitada pela noção de que problemas são questões sobre pertinência a uma linguagem, e não tipos mais gerais de questões.