Upload
maycown-douglas
View
8
Download
0
Embed Size (px)
DESCRIPTION
Trabalho de ICC
Citation preview
TRABALHO 04 – TEORIA DA COMPUTAÇÃO
INTRODUÇÃO À CIÊNCIA DA
COMPUTAÇÃO - COM06850- 2012-II
CIÊNCIA DA COMPUTAÇÃO
GRUPO 5
2012204302 – Caíque de Oliveira de Souza
2012204291 - Heloiza Barros Del’Esposti
2012204278 - Maycown Douglas O. Miranda
2012204279 - Melissa Silva de Souza
2012204298 - Otto Freitas Quintanilha
2012204286 - Paloma Marques Nobre
Teoria da Computação
Primeiro, apresentaremos uma linguagem que chamamos linguagem
simples, a fim demostrar que o numero mínimo de instruções necessárias para
resolver qualquer problema solúvel por um computador é três. Segundo,
explicaremos que outra ferramenta, chamada maquina de Turing também pode
resolver um problema que pode ser resolvido por nossa linguagem simples.
Terceiro, provaremos que nenhum programa pode informar se o outro para ou não
(se o programa termina ou ira executar para sempre), e essa terceira prova, mostra
por si própria que existem problemas que não podem ser solucionado por um
computador.
- As linguagens simples:
Nessa linguagem o único tipo de dados utilizado são números inteiros não
negativos para poder demonstrar algumas ideias da teoria da computação.
Definimos linguagens do computador apenas com três instruções:
• Instrução de incremento: Aumentar de 1 o valor de certa variável.
Imagine que um programa trabalhe com uma variável denominada X e que se
deseje aumentar de 1 o valor dessa variável ou seja incr(x): X = X + 1;
• Instrução de decremento: Esta operação é o inverso daquela
denominada incremento. Trata-se de retirar 1 do valor de uma variável. Então, de
modo análogo à operação de incremento tem-se decr(x): X = X - 1
• Instrução de laço: Trata-se de uma técnica que permite repetir as
mesmas instruções várias vezes ate alcançar o valor desejado da variável.
- A importância da linguagem simples
Embora não seja tão eficiente quanto outras linguagens sofisticadas, é tão
importante quanto, pois a partir dela é possível simular instruções que são
encontradas em algumas linguagens populares com apenas as três instruções da
linguagem simples.
- Macros em linguagem simples
Macroinstrução (macro), chamamos cada simulação de ―macro‖ e os
utilizamos como base para uma nova simulação. O uso de macros facilita a
especificação de trechos repetitivos de um código, que podem ser invocados pelo
programador como uma única linha no programa. Toda macro tem um nome
especificado como o rótulo da pseudo-instrução e que será utilizado pelo
programador para invocar a macro, e um corpo, que será usado pelo macro-
montador para substituir o nome usado pelo programador pela sequência de
instruções nele especificados. Na sua forma mais simples, uma macro é
simplesmente uma abreviatura para um grupo de instruções. A forma geral de
definição de uma macro é:
nome MACRO [argumentos]
corpo
- Entrada e saída
Na linguagem simples, meramente para provar alguns teoremas da
ciência da computação simulamos a saída assumindo que a ultima variável utilizada
é o que deveria ser impresso.
Máquina de Turing
A Máquina de Turing(MT) pode ser descrita como um modelo matemático
do processo de computação. Sua estrutura simples é proposital, uma vez que Turing
pretendia, com sua definição, chegar a um modelo que fosse universalmente aceito.
Atualmente há diversos modelos na literatura, mas que seguem o mesmo princípio.
A MT é o principal modelo usado para o estudo do que é ou não é
computável. De acordo com a tese de Church, todos os modelos razoáveis de
procedimento são equivalentes, e a MT se revelou simples e flexível o suficiente
para permitir todas as demonstrações dos resultados principais. Pode-se usar uma
MT como aceitador ou reconhecedor, ou ainda como a implementação de um
procedimento mais geral, que transforma uma cadeia de entrada em uma cadeia de
saída.
Uma Máquina de Turing é feita de três componentes: fita, controlador
cabeçotes de leitura/gravação.
Uma definição para a Máquina de Turing pode ser: Uma máquina de
Turing M é uma tupla M = < K, Σ, Г, δ, i, F >, onde K é um conjunto (finito, não
vazio) de estados, Г é o alfabeto (finito) de símbolos da fita, Σ Г é o alfabeto de
símbolos de entrada, é a função de transição, i K é o estado inicial, e F K é o
conjunto de estados finais. A função de transição d é um mapeamento : K K
{L, R}. Quando tivermos (q, a) = (p, b, R), a MT M, quando está no estado q, e
lê o símbolo a na fita, escreve o símbolo b na mesma posição em que a estava
escrito, move-se para a direita uma célula, e passa para o estado p. (Idem, para a
esquerda no caso de (q, a) = (p, b, L) ). Por simplicidade, podemos deixar alguns
valores de indefinidos, de maneira que deve ser entendida como uma função
parcial. Atingida uma situação em que o estado é , o símbolo lido é a, e (q, a) é
indefinido, dizemos que a máquina para. Poderíamos, se desejado, definir máquinas
de Turing que sempre param quando atingem um estado final, e que nunca param
em um estado não final. Mas sempre que esta propriedade for desejada, podemos
alterar uma MT introduzindo um estado adicional, não final, do qual a máquina
nunca mais sai.
Tese de Church-Turing
Já dizia a tese de Church-Turing: Se existe um algoritmo para fazer a
tarefa de manipulação de símbolos, existe uma máquina de Turing para realiza-la. A
tese consiste em tentar provar que qualquer tarefa de manipulação de símbolos,
pode ser executada em uma máquina de Turing, caso a tarefa não puder ser
calculada na máquina de Turing, a mesma não pode ser calculada. Essa tese pode
ser provada matematicamente, apesar de que nunca poderá ser provada, existem
fortes argumentos em seu favor. Como por exemplo, não foi encontrado nenhum
algoritmo que não possa ser simulado utilizando uma máquina de Turing, e foi
demonstrado que todos os modelos computacionais que têm sido comprovados são
equivalentes ao modelo dessa máquina.
Numeros de Godel
Na ciência da computação, um numero sem sinal pode ser associado a cada
programa que poderá ser descrito em linguagem especifica, geralmente é chamado
de numero de Godel, em homenagem ao matemático austríaco Kurt Godel.
Vantagens de uso do numero de Godel: Os programas podem ser utilizados
como um único item de dados como entrada para outros programas. A Identificação
de um programa pode ser feita por apenas uma representação de um único numero
inteiro. O uso de numeração pode ser utilizado para provar que certos problemas
não podem ser solucionados pelo computador, mostrando que o número total de
problemas no mundo é maior do que problemas em programas.
Vários métodos foram criados para numeração de programas, um modo de
transformação simples para numerar programas escritos em linguagens simples na
tabela abaixo.
Símbolo Código
Hexadecimal
Símbolo Código
Hexadecimal
1 1 9 9
2 2 Incr A
3 3 Decr B
4 4 while C
5 5 { D
6 6 } E
7 7 X F
8 8
Representação de um Programa
Ao utilizar a tabela, podemos representar qualquer programa escrito na
linguagem simples por um único numero inteiro positivo, basta seguir algumas
etapas.
1. Substituir a partir da tabela, cada símbolo hexadecimal correspondente.
2. Interpretar o número hexadecimal resultante como um número inteiro
sem sinal.
Ex:
Qual numero de Godel para o programa incr(X)?
A solução basta substituir cada símbolo pelo seu correspondente código
hexadecimal.
Incr X – (AF)16 ---- 175
A representação do programa em numero de Godel será 175.
Interpretação de um número de Godel
Para interpretar um numero de Godel em um programa, basta seguir as
seguintes instruções:
1. Converta o número para hexadecimal
2. Interprete cada dígito hexadecimal como um símbolo utilizando a
tabela.
Ex:
Interprete 3058 como um programa.
Basta modificar o numero para hexadecimal e substituir cada dígito pelo
correspondente símbolo.
3508 → (BF2)16 → decr X 2 → decr (X2)
O Problema da Parada
Quase todo o programa escrito de em uma linguagem de programação
envolve alguma forma de repetição, laços ou funções recursivas. Uma construção
com base em repetição pode nunca parar; ou seja, é possível que um programa seja
executado para sempre se tiver um laço infinito. A existência desse programa
poderia economizar muito tempo dos programadores. Executar um programa sem
saber se ele irá ou não parar um trabalho muito difícil. Mas, agora já pode ser
comprovado que um tal programa não pode existir, o que é um grande
desapontamento para os programadores.
O Problema da Parada Não Pode Ser Resolvido
Em vez de afirmar que o programa de teste não existe, e nunca poderá
existir, o cientista da computação diz: ―o problema da parada não é solúvel‖.
Prova: Assumimos que o programa existe, e então, mostramos que essa
existência cria uma contradição; portanto ela não pode existir.
Etapa 1
Assumimos que um programa, chamado teste, existe.
Etapa 2
Criamos outro programa, chamado Estranho, que é composto de duas
partes: uma cópia de teste no início, e um laço vazio.
Etapa 3
Tendo escrito o programa Estranho, testamos consigo mesmo como
entrada.
A Complexidade dos Problemas
A complexidade de um problema é o consumo de tempo do melhor
algoritmo possível para o problema. (O melhor algoritmo conhecido atualmente não
é, em geral, o melhor algoritmo possível.) É claro que estamos nos referindo ao
consumo de tempo no pior caso, ou seja, ao consumo de tempo para as instâncias
mais "difíceis" do problema.
Problemas computacionais
Precisamos de um repertório de exemplos. Seguem alguns:
Quadrado perfeito: Dado um número natural n, encontrar um número
natural x tal que x² = n ou constatar que tal x não existe.
Equação inteira do segundo grau: Dados números inteiros a, b e c,
encontrar um número inteiro x tal que ax² + bx + c = 0 ou constatar que tal xnão
existe.
Fatoração: Dado um número natural n, encontrar um número natural p,
maior que 1 e menor que n, que seja divisor n ou constatar que tal p não existe.
Máximo divisor comum: Encontrar o maior divisor comum de dois
números naturais m e n.
Divisor comum grande: Dados números naturais m, n e k, encontrar um
divisor comum de m e n que seja maior que k ou constatar que tal divisor não
existe.
Subsequência crescente máxima: Dada uma sequência s1,…,sn de
números naturais, encontrar uma subsequência crescente máxima de s1,…,sn.
Subsequência crescente longa: Dada uma sequência s1,…,sn de
números naturais e um número natural k, encontrar uma subsequência crescente de
s1,…,sn que tenha comprimento maior que k ou constatar que tal subsequência
não existe.
Mochila: Dados números naturais p1,…,pn, v1,…,vn e c, encontrar um
subconjunto K de {1,…,n} tal que a soma dos pk para k em K não passe de c e a
soma dos vk para k em K seja máxima.
Caminho mínimo: Dados vértices r e s de um grafo, encontrar um
caminho de comprimento mínimo de r a s no grafo ou constatar que não há
caminho algum de r a s.
Caminho máximo: Dados vértices r e s de um grafo, encontrar um
caminho de r a s que tenha comprimento máximo ou constatar que não há caminho
algum de r a s.
Ciclo máximo: Encontrar um ciclo de comprimento máximo num grafo
dado ou constatar que o grafo não tem ciclo algum.
Ciclo hamiltoniano: Encontrar um ciclo hamiltoniano (ou seja, um ciclo
que passe por todos os vértices) num grafo dado ou constatar que tal ciclo não
existe.
Ciclo longo: Dado um grafo e um número k, encontrar um ciclo de
comprimento maior que k ou constatar que o grafo não tem tal ciclo.
Clique grande: Dado um grafo e um número k, encontrar uma clique com
k ou mais vértices ou constatar que tal clique não existe.
Cobertura pequena: Dado um grafo e um número k, encontrar uma
cobertura com menos que k vértices ou constatar que tal cobertura não existe.
(Observe que há uma relação íntima entre o problema do ciclo
hamiltoniano e o problema do ciclo longo. Há relações análogas entre vários outros
pares de problemas.)
Algoritmos Polinomiais
Dizemos que um algoritmo resolve um dado problema se, ao receber
qualquer instância do problema, devolve uma solução da instância ou diz que a
instância não tem solução.
Um algoritmo que resolve um dado problema é polinomial se o seu
consumo de tempo no pior caso é limitado por uma função polinomial dos tamanhos
das instâncias do problema.
É polinomial, por exemplo, todo algoritmo que consome no máximo
100N4 + 300N2 + 5000 unidades de tempo, sendo N o tamanho da instância.
Também é polinomial todo algoritmo que consome no máximo 200N9 log N
unidades de tempo, por exemplo (pois 200N9 log N < 200N10).
Algoritmos polinomiais são considerados rápidos (ainda que seja difícil
aceitar como rápido um algoritmo que consome tempo proporcional a N500, por
exemplo). Algoritmos não polinomiais — como, por exemplo, os que consomem
tempo proporcional a 2N — são considerados inaceitavelmente lentos (ainda que
possam ser úteis para valores muito modestos de N).
Problemas Polinomiais e a Classe P
Um problema computacional é polinomial se existe um algoritmo
polinomial para o problema. Problemas desse tipo são considerados tratáveis do
ponto de vista computacional.
Exemplos de problemas polinomiais: o problema da equação do segundo
grau, o problema do máximo divisor comum, o problema do caminho mínimo,
oproblema da subsequência crescente máxima.
A classe P de problemas é o conjunto de todos os problemas polinomiais.
[A rigor, esta definição está incorreta, pois a classe P contém apenas os problemas
polinomiais de decisão.]
Um problema é não polinomial se nenhum algoritmo polinomial resolve o
problema. (Cuidado: não se trata de problemas para os quais algoritmos
polinomiais não são conhecidos atualmente, pois tais algoritmos podem vir a ser
descobertos no futuro.) Problemas desse tipo são considerados
computacionalmente intratáveis.
A Classe NP de Problemas
O status de muitos problemas é desconhecido: não se sabe se o
problema é polinomial ou não. Diante disso, é uma boa ideia investigar a
complexidade relativa dos problemas. Trata-se de verificar se um dado problema Y é
computacionalmente mais fácil ou mais difícil que um outro problema X (talvez mais
bem-conhecido). Antes que isso possa ser feito, entretanto, é preciso restringir um
pouco o universo dos problemas sob estudo.
Quais problemas devemos considerar "razoáveis"? Diremos que um
problema é "razoável" se é fácil reconhecer uma solução do problema quando se
está diante de uma. Mais precisamente, um problema computacional X é "razoável"
se toda instância I de X satisfaz a seguinte condição:
é possível verificar, em tempo polinomial, se uma suposta solução da
instância I é, de fato, uma solução de I.
Muitos dos problemas mencionados acima são "razoáveis". Considere os
seguintes exemplos:
É fácil verificar se um dado inteiro x satisfaz a equação ax² + bx + c = 0.
Essa verificação consome tempo limitado por um polinômio no tamanhosda instância
(a,b,c) pois o valor absoluto de qualquer solução x da equação não é maior que o
produto (|a|+1)(|b|+1)(|c|+1). Portanto, oproblema da equação do segundo grau é
"razoável".
É fácil verificar se um dado número natural p divide n. Ademais, a
verificação consome tempo polinomial no tamanho da instância pois todo divisor de
n é menor que n. Portanto, o problema da fatoração é "razoável".
O problema do ciclo longo é "razoável" pois é fácil verificar, em tempo
polinomial, se um dado objeto é um ciclo no grafo e tem comprimento maior que k.
O problema do ciclo hamiltoniano é "razoável" pois é fácil verificar, em
tempo polinomial, se um dado objeto é um ciclo no grafo e passa por todos os
vértices do grafo.
O conjunto de todos os problemas "razoáveis" é (essencialmente) igual à
classe NP de problemas. [Cuidado: "NP" não é abreviatura de "não polinomial" mas
sim de "nondeterministic polynomial".]
(A rigor, não é correto confundir o conjunto dos problemas "razoáveis"
com classe NP. A questão é que o conceito de solução — que serve de base para a
ideia de problema "razoável" — não é suficientemente preciso. Considere, por
exemplo, o problema do ciclo máximo. Por um lado, parece que deveríamos aceitar
o problema como "razoável" uma vez que o problema do ciclo longo é "razoável".
Por outro lado, o problema não parece "razoável" pois não está claro como verificar
se um dado ciclo é máximo, ou seja, se não existe ciclo mais longo. Para contornar
essas dificuldades, é preciso restringir nossa atenção a problemas "de decisão" e
trocar o conceito de "solução" pelo de "certificado".)
A Questão "P = NP?"
Não é difícil entender que a classe NP inclui a classe P, ou seja, que todo
problema polinomial é "razoável". O bom senso sugere que P é apenas uma
pequena parte de NP. Surpreendentemente, ninguém conseguiu ainda encontrar
um problema de NP que comprovadamente não esteja em P, isto é, um problema
"razoável" para o qual comprovadamente não existe algoritmo polinomial.
Esta situação abre caminho para a suspeita de que talvez P seja igual a
NP . A maioria dos especialistas não acredita nessa possibilidade, entretanto. [O
Instituto Clay de Matemática oferece um prêmio de um milhão de dólares pela
solução da questão "P=NP?".]
A pergunta "P = NP?" por ser reformulada assim: "É verdade que todo
problema cujas soluções podem ser conferidas por um algoritmo polinomial pode
também ser resolvido por um algoritmo polinomial?"
Complexidade Relativa de Problemas
Podemos agora dizer algo sobre a complexidade relativa de problemas
em NP. Um problema Y não é mais difícil que um outro problema X se Y for um
"subproblema", ou "caso particular", de X. Exemplos:
O problema do quadrado perfeito não é mais difícil que o problema da
equação do segundo grau.
O problema do ciclo hamiltoniano não é mais difícil que o problema do
ciclo longo.
O problema da cobertura pequena de um grafo não é mais difícil que o
problema da clique grande. (Por que?)
Para explicar um pouco melhor o conceito, diremos que um problema Y
não é mais difícil que um problema X se qualquer algoritmo polinomial para X pode
ser transformado num algoritmo polinomial para Y. O algoritmo para Y tem três
etapas: primeiro, qualquer instância J de Y é transformada, em tempo polinomial,
numa instância "equivalente" I de X; depois, a instância I é submetida ao algoritmo
polinomial para X, que produz uma solução S; finalmente, S é transformada, em
tempo polinomial, numa solução T de J. Assim, se X está em P e Y não é mais
difícil que X então Y também está em P.
Problemas Completos em Np
Um problema X é completo em NP, ou NP-completo, se X está em NP e
todos os demais problemas em NP não são mais difíceis que X.
A existência de problemas completos em NP é um fato surpreendente e
fundamental. O fato foi demonstrado, por volta de 1970, por S. Cook e L. Levin
(independentemente).
Para mostrar que P = NP basta encontrar um algoritmo polinomial para
um único problema NP-completo. Portanto, os problemas NP-completos são os
mais "difíceis" de NP.
Por exemplo, são NP-completos os problemas (de decisão derivados dos)
seguintes: ciclo hamiltoniano, ciclo longo, clique grande, cobertura
pequena,mochila. Uma lista com centenas de outros problemas NP-completos
pode ser encontrada no livro de Garey e Johnson.
Apêndice: Certificados de Inexistência de Solução
Nossa tentativa grosseira de definir a classe NP de problemas, trouxe à
baila uma questão importante. Muitos problemas têm instâncias que não admitem
solução. Como é possível "provar" ou "certificar" que uma dada instância de um
problema não admite solução? Não estamos tratando aqui de como descobrirque
uma dada instância não tem solução, mas apenas de, uma vez descoberta a
inexistência de solução, como tornar este fato "evidente".
Para muitos problemas, existem certificados naturais e elegantes para a
inexistência de solução. (Todos consistem em trocar um "não existe x" por um
"existe y" apropriado.) Eis alguns exemplos:
Problema do quadrado perfeito: Para mostrar que um número natural n
não é um quadrado perfeito, basta exibir um número natural k tal quek² < n < (k+1)².
Um tal k é um certificado de inexistência de solução.
Problema da equação do segundo grau: Para mostrar que não existe um
inteiro x tal que ax² + bx + c = 0, basta verificar que (1) b²−4ac não é um quadrado
perfeito, ou (2) b²−4ac é um quadrado perfeito mas nem (b²−4ac)½ + b nem
(b²−4ac)½ − b são divisíveis por 2a. Assim, um certificado de inexistência de
solução consiste em certificados para as condições (1) ou (2).
Problema do divisor comum grande: Para mostrar que não existe divisor
comum de m e n que seja maior que k é suficiente exibir números inteiros (não
necessariamente positivos) x e y tais que 0 < xm+yn ≤ k. Como qualquer divisor
comum de m e n é também divisor de xm+yn, concluímos que todo divisor comum
de m e n é menor ou igual a k. Assim, o par x,y é um certificado da inexistência de
divisor comum grande. Todas as instâncias sem solução têm um tal certificado. (O
algoritmo de Euclides calcula o certificado ao mesmo tempo que calcula um máximo
divisor comum.)
Problema da fatoração: Existe um certificado muito interessante para as
instância do problema que não têm solução, mas não tenho condições descrever o
certificado aqui.
Problema da subsequência crescente longa: Suponha dada uma
cobertura por subsequências estritamente decrescentes de uma sequência
s1,…,snde números naturais. Se a cobertura consiste em k sequências então é claro
que nenhuma subsequência crescente de s1,…,sn pode ter comprimento maior que
k. Assim, uma cobertura pequena por subsequências estritamente decrescentes é
um certificado de inexistência de subsequência crescente longa. Todas as
instâncias sem solução têm uma tal cobertura. (O algoritmo que calcula uma
subsequência crescente máxima pode ser adaptado para determinar também um
cobertura mínima.)
Problema do caminho mínimo: Seja R um conjunto de vértices que
contém o vértice r mas não contém o vértice s. Suponha ainda que nenhuma aresta
liga um vértice de R a um vértice fora de R. Se um tal R existe, é claro que não
existe caminho de r a s. Assim, um tal R é um certificado de inexistência de solução
para a instância em discussão. (Toda instância sem solução tem um tal certificado.)
Cada um desses exemplos mostra que o correspondente problema está
na classe coNP.
Referências Bibliográficas
[1] FOROUZAN, Behrouz; MOSHARRAF, Firouz. Fundamentos da Ciência da
Computação, tradução da 2ª edição internacional.
[2] http://www.ime.usp.br