Skip to content

Instantly share code, notes, and snippets.

@marciojrtorres
Last active July 23, 2017 16:14
Show Gist options
  • Save marciojrtorres/5bf013f419e3dc4a51bcc59d8c41ff44 to your computer and use it in GitHub Desktop.
Save marciojrtorres/5bf013f419e3dc4a51bcc59d8c41ff44 to your computer and use it in GitHub Desktop.
Trabalho destinado à disciplina de Elementos da Teoria da Computação e Automação, aos cuidados do professor Dr. Leonardo Emmendorfer

Computabilidade e Complexidade

Trabalho destinado à disciplina de Elementos da Teoria da Computação e Automação, aos cuidados do professor Dr. Leonardo Emmendorfer.

Discente: 111989 - Márcio J. Ramos Torres Data: 2016-05-09

Disponível on-line no endereço https://gist.github.com/marciojrtorres/5bf013f419e3dc4a51bcc59d8c41ff44

Introdução

Desde o começo da computação teórica e da resolução de problemas em sua representação algorítmica, certas questões intrigam e interessam aos cientistas da computação em relação a resolução de problemas, quais sejam:

  • quando um problema pode ser representado algoritmicamente: saber que suas entradas são aceitas pela máquina;
  • quando um problema pode ser resolvido: saber se é possível computá-lo e receber uma resposta;
  • qual o custo de sua solução: avaliando, sendo um problema computável, a complexidade e demanda de tempo e espaço necessário para obter uma resposta em tempo útil.

A avaliação dessas questões, na Teoria da Computação, pode ser realizada com a verificação dos problemas sendo representados e executados em um modelo de computador de propósito geral, a saber, a Máquina de Turing (MT). A MT não é o único modelo disponível para realizar experimentos de computabilidade e a expressão de algoritmos, da mesma forma que existem linguagens de programação diferentes hoje em dia que podem, embora não exatamente da mesma maneira, expressar o mesmo resultado.

Outra notação que é extremamente importante para o entendimento de algoritmos e sua definição veio do trabalho de Alonzo Church, que é conhecida como λ-cálculo. Ambas definições, de Turing e Church, são equivalentes, sendo, por exemplo, baseadas no conceito de uma memória infinita. A conexão desses dois modelos, possibilitou uma melhor definição de algoritmos e os computadores que poderiam executá-lo, sendo chamada de Tese de Church-Turing. Em síntese, um algoritmo deve consistir em um conjunto finito de instruções escrito com um conjunto finito de símbolos e produzir resultado em um número finito de passos.

As definições de MT e algoritmo, permitem verificar se, então, uma dada linguagem é reconhecida por uma MT - se ela é Turing-reconhecível - e se produz uma resposta, sendo aceita ou rejeitada - se ela é Turing-decidível. Concernente a finitude de passos para a resolução do problema, propriedade essencial de um algoritmo, por vezes o problema pode depender de demasiada quantidade de etapas, característica conhecida como Complexidade, fazendo com que nem sempre o algoritmo descrito seja viável, carecendo de uma descrição que realize a computação em menos etapas. As características de decidibilidade e complexidade serão descritas nos tópicos a seguir.

Decidibilidade

A decidibilidade explora a solubilidade de problemas através da representação algoritimica, segundo a Tese de Chuch-Turing. Saber se um dado problema é solúvel (decidível, computável) é importante para qualquer cientista da computação. Reconhecer, antecipadamente, se um problema é indecidível pode poupar esforços buscando uma simplificação do problema antes de realmente implementá-lo. Ter noções do que é não-computável também pode oferecer um vislumbre das classes de problemas que podem ser (ou não) um desafio para pesquisadores.

Um primeiro passo para verificar a decidibilidade é observar a entrada determinando se esta pode ser enumerável. É um questão desafiadora, já que as MTs lidam com o conceito de memória infinita, mas que de fato nem todo conjunto infinito poderia ser computável. Então, para medir se um conjunto infinito de entrada é enuméravel, pode ser usar a técnica de diagonalização.

A diagonalização foi descoberta pela matemático Georg Cantor, sendo usada para medir os tamanhos de conjuntos infinitos. A técnica não é muito complexa, surgindo da necessidade de comparar conjuntos infinitos sem ter de recorrer à contagem, fazendo, então, o emparelhamento dos elementos de dois conjuntos infinitos buscando demonstrar que há uma correspondência entre eles. Por exemplo, para confirmar a enumerabilidade dos conjuntos dos números naturais e seu quadrado, basta demonstrar que ambos têm o mesmo tamanho emparelhando n e sua função quadrado f(n) = n * n, como a seguir:

 n | f(n)
---------
 1 | 1
 2 | 4
 3 | 9
 ...  ...

Com base nesse princípio, sabe-se que nem toda entrada pode ser enumerada, pois não pode ser representada por uma tabela de correspondência com outro conjunto infinito. Um exemplo bem conhecido é o conjunto de números reais. A incontabilidade de algumas linguagens evita que estas sejam usadas nas MTs, NÃO sendo consideradas Turing-reconhecíveis e, portanto, não sendo computáveis. Esse conceito é fundamental para entender o Problema da Parada no tópico a seguir.

O Problema da Parada

O Problema da Parada é um exemplo de problema indecidível. A questão é relativamente simples: dada uma MT que reconheça outra MT', aceitando a entrada se MT' aceita sua entrada ou rejeitando se MT' rejeita. No Problema da Parada, essa MT entra em loop sobre a entrada, pois enquanto a aceitação demonstra que a MT realmente pára, a rejeição não pode ser verificada pois não há como saber em que tempo a sub-rotina pára. A formalização do Problema da Parada é:

Amt = {<M, w>|M é uma MT e M aceita w}

Para provar a indecidibilidade do problema definimos uma MT H que executada sobre a entrada <M, w>, onde M é uma MT e w uma cadeia, H pára e aceita se M aceita w e pára e rejeita se M rejeita w. Então, definimos outra MT D para determinar o que a MT M faz quando recebe a si mesma (M) como cadeia. D deve fazer o oposto, rejeitar se M aceita M e aceitar caso contrário. Após essas definições rodamos D tendo como entrada ela mesma D. Se independente do que D faz ela é forçada a fazer o oposto, isso denota uma contradição, levando ao entendimento que ambas MTs não podem existir.

O complemento de uma MT de uma MT não é sequer Turing-reconhecível, levando a conclusão de que uma linguagem é decidível quando ela e seu complemento são ambas Turing-reconhecíveis.

Complexidade

Mesmo quando os problemas são Turing-decidíveis resta saber se a sua solução pode ser encontrada em tempo hábil, em outras palavras, a quantidade de tempo necessária para resolver um problema. Essa característica é conhecida como Complexidade de Tempo.

A aferição de Complexidade é realizada através da Análise do Algoritmo, antecipando a quantidade de passos necessários para a solução de um problema particular. Entretanto, a quantidade de passos depende de fatores como o tamanho e tipo da entrada, por exemplo. Neste sentido, o mesmo algoritmo pode ser bastante rápido para uma entrada e inviável para outra. Cada instância particular do problema pode consumir um tempo diferente. Por este motivo, a análise de um algoritmo considera:

  • o pior caso: em que condições, mais extremas, um algoritmo pode executar e o tempo que ele leva;
  • o caso médio: entre várias execuções com condições variáveis, qual é a média de tempo;
  • o melhor caso: em qual condição o algoritmo tem seu melhor desempenho.

Normalmente, os cientistas têm interesse no pior caso, em outras palavras, o que levará o tempo mais longo para computar uma resposta.

Notação Assintótica

Geralmente não é possível medir com precisão o tempo de execução de algoritmo, por este motivo geralmente ele é estimado. Para fazer esta estimativa faz-se uso de uma notação especial conhecida como Análise Assintótica, aplicada, normalmente, a entradas grandes.

Entre vários passos de um algoritmo e suas instruções, algumas instruções são executadas com mais frequência. A quantidade de execuções dessas instruções frequentes, ignorando as instruções esporadicamente executadas, que determina a análise assintótica e define um limite de execução, entre os mais conhecidos o o-pequeno e O-grande.

A análise assintótica sempre leva em conta, dada uma expressão, o termo de mais alta ordem, por exemplo: 5n³ + 2n² + 4n, recebe a notação assintótica de O(n³), sendo que todo problema que pode ser resolvido em tempo O(n^k) é conhecido como algoritmo de Tempo Polinomial.

Em Teoria da Computação essas análises são realizadas com base em Máquinas de Turing Determinísticas (MTD) e Não-Determinísticas (MTND). Neste sentido, os problemas são classificados segundos a sua solubilidade em Tempo Polinomial em uma dessas máquinas. Por exemplo, todos os problemas solúveis em Tempo Polinomial por uma MTD são conhecidos como classe P, que será visto a seguir.

A Classe P

Os problemas classificados como P são considerados solúveis em Tempo Polinomial por uma Máquina de Turing Determinística. Os problemas nessa classes podem ser resolvidos com tempo de execução O(n^k) com um k constante.

Vários problemas clássicos e bem conhecidos estão na Classe P, por exemplo, cálculo do MDC e determinar se um número é primo.

A Classe NP

Para alguns problemas não foram encontrados algoritmos de Tempo Polinomial (o que não quer dizer que não existam), sendo conhecidos por serem, então, problemas difíceis de resolver (mesmo, e especialmente, para um computador). A Classe NP é usada para agrupar os problemas que são solúveis em Tempo Polinomial por uma Máquina de Turing Não-Determinística (o N de NP é de Non-deterministic Polynomial), mas que são verificáveis em tempo polinomial por uma MTD, em outras palavras, testar a solução e validá-la é mais simples que descobrí-la.

Existem muitos problemas em NP, a maioria de forte interesse dos pesquisadores e utilidade para a ciência, onde é um desafio encontrar um algoritmo que os resolva em tempo polinomial.

A Classe NP-Completo

NP-Completo é um subconjunto da classe NP de problemas contendo os exemplares considerados mais difíceis. Todos os problemas NP-Completo não tiveram soluções encontradas que sejam executadas em tempo polinomial (e que parecem distantes de serem encontradas).

Existem vários problemas NP-Completo que podem ter uma solução aproximada, através de métodos probabilísticos, restringindo a entrada e com heurísticas.

Cientistas se esforçam para provar a NP-Completude de um dado problema que geralmente é semelhante a outros muitos problemas da Classe NP. Neste sentido, sabendo-se que tal problema NP-Completo é solúvel em tempo polinomial, uma grande quantidade de problemas NP na mesma categoria também seriam.

Considerações finais

A Máquina de Turing e a computação teórica oferecem um arcabouço essencial para a compreensão de computabilidade, muito além da implementação e tentativa exaustiva de solução de problemas. Isso se estende às noções de decidibilidade e complexidade, que são utensílios não só à teoria mas também à computação aplicada e projetos de pesquisa onde problemas complexos devem ser modelados computacionalmente e podem (devem) ser classificados quando a sua complexidade.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment