Como os Algoritmos Funcionam
Chapter 10 Intractable Problems and Approximation Algorithms

Capítulo 10: Problemas Intratáveis e Algoritmos de Aproximação

Nos capítulos anteriores, exploramos uma ampla variedade de algoritmos para resolver problemas de forma eficiente. No entanto, existem muitos problemas para os quais nenhum algoritmo eficiente é conhecido. Neste capítulo, discutiremos a teoria da NP-completude, que fornece uma maneira de mostrar que um problema provavelmente é intratável, ou seja, que provavelmente não há um algoritmo eficiente para resolvê-lo. Também exploraremos técnicas para lidar com problemas NP-completos, incluindo algoritmos de aproximação e algoritmos de busca local.

Classes P e NP

Para entender a NP-completude, primeiro precisamos definir duas classes importantes de problemas: P e NP.

A classe P (tempo polinomial) consiste em todos os problemas de decisão que podem ser resolvidos por um algoritmo que executa em tempo polinomial. Um problema de decisão é um problema que tem uma resposta sim ou não. Por exemplo, o problema de determinar se um grafo tem um ciclo hamiltoniano (um ciclo que visita cada vértice exatamente uma vez) é um problema de decisão. Se um problema de decisão está em P, então existe um algoritmo que pode resolver qualquer instância do problema em um número de etapas limitado por uma função polinomial do tamanho da entrada.

A classe NP (tempo não determinístico polinomial) consiste em todos os problemas de decisão para os quais uma solução pode ser verificada em tempo polinomial. Por exemplo, o problema do ciclo hamiltoniano está em NP porque, dado um grafo e um ciclo hamiltoniano proposto, podemos facilmente verificar em tempo polinomial se o ciclo proposto é realmente um ciclo hamiltoniano.

É claro que P é um subconjunto de NP, porque qualquer problema que pode ser resolvido em tempo polinomial também pode ser verificado em tempo polinomial. No entanto, é uma questão em aberto se P = NP. A maioria dos especialistas acredita que P ≠ NP, o que significa que existem problemas em NP que não estão em P. No entanto, provar isso seria um grande avanço na ciência da computação teórica.

NP-Completude

Um problema de decisão X é NP-completo se:

1Aqui está a tradução em português deste arquivo markdown. Para o código, não traduzi o código, apenas os comentários.

. X está em NP, e 2. Cada problema em NP pode ser reduzido a X em tempo polinomial.

Um problema Y é reduzível a um problema X se qualquer instância de Y pode ser transformada em uma instância de X em tempo polinomial, de modo que a resposta à instância de Y seja "sim" se e somente se a resposta à instância transformada de X seja "sim".

O conceito de NP-completude foi introduzido por Stephen Cook e Leonid Levin independentemente em 1971. O primeiro problema demonstrado ser NP-completo foi o Problema de Satisfatibilidade Booleana (SAT). Muitos outros problemas desde então foram demonstrados ser NP-completos, reduzindo o SAT ou outros problemas NP-completos conhecidos a eles.

Alguns problemas NP-completos bem conhecidos incluem:

  • O Problema do Caixeiro Viajante (TSP): Dada um conjunto de cidades e as distâncias entre elas, encontre o caminho mais curto que visite cada cidade exatamente uma vez.
  • O Problema da Mochila: Dado um conjunto de itens com pesos e valores, e uma mochila com um limite de peso, encontre o subconjunto de itens com o valor total máximo que cabe na mochila.
  • O Problema de Coloração de Grafos: Dado um grafo, encontre o número mínimo de cores necessárias para colorir os vértices de modo que nenhum dois vértices adjacentes tenham a mesma cor.

A importância da NP-completude é que se qualquer problema NP-completo pudesse ser resolvido em tempo polinomial, então todos os problemas em NP poderiam ser resolvidos em tempo polinomial (ou seja, P = NP). No entanto, apesar de décadas de esforço, nenhum algoritmo em tempo polinomial foi encontrado para qualquer problema NP-completo. Isso sugere (mas não prova) que os problemas NP-completos são intrinsecamente difíceis e é improvável que tenham algoritmos eficientes.

Algoritmos de Aproximação

Como os problemas NP-completos acredita-se serem intratáveis, muitas vezes recorremos a algoritmos de aproximação quando enfrentamos tais problemas na prática. Um algoritmo de aproximação é um algoritmo que encontra uma solução garantida de estar dentro de um certo fator da solução ótima.

Por exemplo, considere o problema da Cobertura de Vértices: dado um grafo, encontre o menor conjunto de vértices que cobre todas as arestas.Aqui está a tradução em português do arquivo Markdown, com os comentários traduzidos, mas o código não traduzido:

Encontrar um conjunto mínimo de vértices tal que cada aresta seja incidente a pelo menos um vértice no conjunto. Este problema é NP-completo. No entanto, existe um algoritmo de aproximação simples que encontra uma cobertura de vértices que é no máximo o dobro do tamanho da cobertura de vértices ótima:

  1. Inicialize um conjunto vazio C.
  2. Enquanto houver arestas não cobertas no grafo:
    • Escolha uma aresta não coberta arbitrária (u, v).
    • Adicione tanto u quanto v a C.
    • Remova todas as arestas incidentes a u ou v do grafo.
  3. Retorne C.

Este algoritmo é executado em tempo polinomial e sempre encontra uma cobertura de vértices que é no máximo o dobro do tamanho da cobertura de vértices ótima. Para ver isso, observe que em cada iteração, o algoritmo escolhe dois vértices para cobrir uma aresta, enquanto a solução ótima deve escolher pelo menos um desses vértices. Portanto, o algoritmo escolhe no máximo o dobro do número de vértices da solução ótima.

Algoritmos de aproximação são frequentemente usados na prática porque fornecem um nível garantido de qualidade enquanto são executados em tempo polinomial. A razão de aproximação de um algoritmo é a pior razão entre o tamanho da solução encontrada pelo algoritmo e o tamanho da solução ótima.

Algoritmos de Busca Local

Outra abordagem para lidar com problemas NP-completos é usar algoritmos de busca local. Um algoritmo de busca local começa com uma solução inicial e a melhora iterativamente fazendo pequenas alterações locais até que não seja possível fazer mais melhorias.

Por exemplo, considere o Problema do Caixeiro Viajante (TSP). Um algoritmo de busca local simples para TSP funciona da seguinte forma:

  1. Comece com um tour arbitrário.
  2. Enquanto puderem ser feitas melhorias:
    • Considere todas as possíveis trocas de duas cidades no tour atual.
    • Se alguma troca melhorar o comprimento do tour, realize a troca.
  3. Retorne o tour atual.

Este algoritmo começa com um tour aleatório e o melhora repetidamente trocando pares de cidades, até que não seja possível fazer mais melhorias. O tour resultante é um ótimo local, o que significa que não pode ser melhorado.Aqui está a tradução em português do arquivo markdown, com os comentários do código traduzidos:

Algoritmos de busca local muitas vezes podem encontrar boas soluções rapidamente, mas não há garantia de que eles encontrarão o ótimo global. Eles podem ficar presos em ótimos locais que estão longe do ótimo global. Para mitigar isso, várias técnicas podem ser usadas, como:

  • Executar a busca local várias vezes com diferentes soluções iniciais.
  • Permitir que a busca local faça movimentos que piorem temporariamente a solução, para ajudar a escapar de ótimos locais.
  • Usar estruturas de vizinhança mais complexas que considerem mudanças maiores na solução atual.

Algoritmos de busca local são amplamente utilizados na prática para resolver grandes instâncias de problemas NP-completos, muitas vezes em combinação com outras técnicas, como algoritmos de aproximação e heurísticas.

Conclusão

A teoria da NP-completude fornece uma estrutura para entender a dificuldade inerente de certos problemas computacionais. Problemas NP-completos acredita-se serem intratáveis, ou seja, é improvável que tenham algoritmos eficientes.

Quando nos deparamos com problemas NP-completos na prática, muitas vezes recorremos a algoritmos de aproximação e algoritmos de busca local. Algoritmos de aproximação fornecem um nível garantido de qualidade da solução, enquanto são executados em tempo polinomial. Algoritmos de busca local muitas vezes podem encontrar boas soluções rapidamente, melhorando iterativamente uma solução inicial.

Entender a teoria da NP-completude e as técnicas para lidar com problemas NP-completos é essencial para qualquer pessoa que trabalhe com problemas de otimização do mundo real. Embora não possamos resolver problemas NP-completos de forma ótima, muitas vezes podemos encontrar soluções suficientemente boas usando algoritmos de aproximação e algoritmos de busca local.

À medida que o tamanho e a complexidade dos problemas que enfrentamos continuam a crescer, a importância de entender e lidar com a NP-completude só aumentará. Dominando as técnicas abordadas neste capítulo, você estará bem equipado para enfrentar alguns dos problemas mais desafiadores e importantes da ciência da computação.Here is the Portuguese translation of the provided Markdown file, with the code comments translated:

Ciência e além

O que é a ciência?

A ciência é um método sistemático de obter conhecimento sobre o mundo ao nosso redor. Ela envolve a observação, a formulação de hipóteses, a realização de experimentos e a análise de dados para entender os fenômenos naturais.

# Exemplo de código Python que calcula a média de uma lista de números
numbers = [5, 10, 15, 20, 25]
average = sum(numbers) / len(numbers)
print(f"A média dos números é: {average}")

Por que a ciência é importante?

A ciência nos ajuda a entender melhor o mundo em que vivemos. Ela nos fornece ferramentas para resolver problemas, tomar decisões informadas e melhorar a nossa qualidade de vida. Além disso, a ciência é a base para muitas das tecnologias que usamos no dia a dia.

Além da ciência

Embora a ciência seja fundamental, ela não é a única forma de conhecimento. Existem outras maneiras de entender o mundo, como a filosofia, a arte e a espiritualidade. Essas abordagens complementam a ciência e nos ajudam a ter uma visão mais ampla e profunda da realidade.