Resposta:
Resposta:
Resposta:
Resposta:
Resposta:
O que fazer:
Resposta: Para provar que um problema é
-completo, deve-se provar que ele é
um problema
e que é
-difícil. As provas serão apresentadas em seguida.
Dado o problema de decisão relacionado, enunciado a seguir:
``Dado um grafo não orientado completo ponderado, determinar se um certo caminho
percorre uma distância menor ou igual a um valor
, sendo
cada um dos vértices do conjunto
, em qualquer ordem.''
A solução para este problema pode ser modelada pelo pseudo-código a seguir, onde
é a matriz de adjacência:
;
Para
até
Faça
;
;
Retorne
;
De fato, este algoritmo é executado em tempo polinomial - mais especificamente, em
. Portanto:
Dada uma instância
para o PCH, deve-se construir um grafo
,
onde, para cada aresta
, temos que o custo
dessa aresta é
Além disto, o grafo
é completo, ou
A partir deste ponto,
pode ser uma instância para o PCV: é um grafo completo,
ponderado e bem definido. Para fechar a questão, deve-se provar que: (i) a redução pode
ser feita em tempo polinomial; (ii)
tem solução para o PCH somente se
tem
solução para o PCV com custo máximo 0; e (iii) vice-versa -
tem solução para o
PCV com custo máximo 0 somente se
tem solução para o PCH.
Com base nas três provas apresentadas, deduzimos então que
Finalmente, com base nas deduções (1) e (3), concluímos que
o Problema do Caixeiro Viajante é
-completo.
Sobre o algoritmo: O algoritmo implementado é bastante simples. Basicamente, é uma adaptação do algoritmo de pesquisa primeiro em profundidade (Depth-First Search), com duas alterações básicas:
Maior problema resolvido: O maior problema que se conseguiu resolver possui 14 vértices e demorou pouco mais de 28 minutos para ser processado. O problema com 15 vértices foi interrompido após cerca de 3 horas, tendo sido considerado insolúvel em tempo razoável para a plataforma de execução adotada.
É importante ressalvar que esses tempos foram conseguidos sem qualquer otimização sobre o algoritmo de força bruta. Especificamente, não foi implementada a checagem de limite de distâncias percorridas, que permite que uma determinada tentativa seja interrompida caso se perceba que ela necessariamente levará a uma solução pior do que a que foi encontrada até o momento. Isto porque uma implementação desse tipo influenciaria a cronometragem, pois é possível que a instância proposta seja particularmente boa ou ruim para esta abordagem.
A Tabela 1 sumariza os tempos obtidos com as execuções do algoritmo adotando quantidades crescentes de vértices.
Projeção de tempo para uma entrada dez vezes maior do que a do maior problema resolvido: É muito interessante plotar os tempos obtidos em um gráfico logarítmico na ordenada, que nos dá uma boa noção do comportamento do crescimento de tempo em função do número de vértices:
De fato, há uma conformação exponencial no crescimento da função de tempo. A resolução da questão proposta é feita em três passos:
|
Dessas equações obtemos os valores de
e
:
Para observar a veracidade da equação proposta, a Figura 2 sobrepõe a plotagem dos tempos projetados (linha sólida vermelha) aos tempos obtidos (linha tracejada azul), demonstrando uma conformação bastante adequada.
|
|
Finalmente, o resultado da questão: Basta calcular o valor de
para
:
Descrição do algoritmo: O algoritmo implementado é uma simplificação do algoritmo de Christofides, com a aplicação de um algoritmo de melhoramento de resultados por permutação de vértices.
Basicamente, o algoritmo proposto funciona da seguinte maneira:
A diferença entre este algoritmo e o proposto por Christofides está no passo (ii). Enquanto Christofides estabelece o uso do matching mínimo, neste algoritmo deciciu-se pelo uso de um algoritmo guloso, descrito a seguir:
Este algoritmo guloso, apesar de ser executado em tempo bem menor do que o matching mínimo, evidentemente não garante a obtenção do melhor resultado possível.
O algoritmo de melhoramento aplicado ao final consiste dos seguintes passos:
Este pequeno algoritmo de melhoramento é capaz de aprimorar sensivelmente os resultados
obtidos. Para o programa escrito, adotou-se
.
Análise de complexidade: Um bom critério para analisar a complexidade deste algoritmo está em contabilizar o número de comparações de custos de arestas. De fato, ao observar o código-fonte do programa, é possível observar que esta operação ocorre em todas as recursões e em todos os laços relevantes da implementação.
As rotinas do módulo priorqueue.c abstraem a comparação de chaves através de chamadas a função Compare( ). Este módulo representa a implementação padrão de heaps para filas de prioridades, portanto pode-se adotar a análise tradicional vista em Cormen [1] para as rotinas de manutenção do heap. Isto porque a operação de comparação é determinante para o tempo de execução de todas as rotinas deste módulo. Assim, temos:
As rotinas do módulo heuristica.c que compõem a estrutura do algoritmo são analisadas em seqüência:
O laço das linhas 367-378 não realiza nenhuma comparação de chaves, nem o laço das
linhas 391-399. Já o laço das linhas 404-419 é executado no máximo duas vezes o número
de vértices ímpares, que por sua vez é no máximo o número de vértices; o único comando
relevante é o da linha 405, que executa
comparações; portanto, o máximo
possível de comparações do laço é igual a
.
As linhas 433-472 correspondem a três laços aninhados, e cada iteração interna (linhas
438-469) chama 5 vezes a rotina calcDistCaminho( ); entretanto, como o
seu custo em comparações é
, estes laços aninhados não contam comparações.
Finalmente, todos esses passos estão incluídos em um grande laço externo (linhas
296-476) que é executado
vezes. Portanto, o custo total da heurística, em termos
de comparações de chaves, é
Melhor caminho obtido: De acordo com a heurística proposta, o caminho mais curto encontrado para a instância do problema brazil58 foi:
20, 28, 35, 16, 25, 5, 18, 27, 13, 36, 33, 14, 45, 55, 44, 32, 7, 21, 54, 53, 1, 40, 47, 2, 34, 9, 51, 50, 46, 48, 42, 26, 4, 22, 11, 56, 23, 57, 43, 17, 0, 29, 12, 39, 24, 8, 31, 19, 52, 49, 3, 6, 30, 37, 41, 15, 10, 38, 20
![]()
Este caminho percorre uma distância de 27.024 unidades - cerca de 6,41% maior do que o melhor caminho encontrado até hoje, de 25.395 unidades.
Resposta: Para realizar esta análise, algumas convenções serão estabelecidas:
é o custo do caminho ótimo do PCV; é o conjunto de arestas que compõem a Árvore Geradora Mínima; é cada uma das arestas pertencentes a , com
;
é o conjunto de arestas que compõem o matching; é cada uma das arestas pertencentes a , com
.
Considere a AGM formada pelas arestas
. A soma dos custos de cada uma destas arestas é
menor do que o custo do caminho ótimo para o PCV, ou
Analisando agora as arestas
: Pela desigualdade de triângulos, cada aresta
não pode ser maior do que o correspondente segmento da solução ótima para o PCV,
portanto
Ao considerar a união das arestas de
e
, obtém-se um caminho euleriano cujo
custo é
Custo
Como este caminho será percorrido fazendo-se curto-circuitos, espera-se que no caso médio
sejam obtidos resultados bem melhores do que
.
Testes realizados: Foi realizado um experimento com 1.000 entradas aleatoriamente geradas de instâncias com 10 vértices2. Os experimentos constaram em executar a heurística e comparar seus resultados (custo total dos caminhos) com o obtido através da força bruta. Os resultados obtidos foram interessantes, pois demonstram a eficiência do algoritmo obtido:
Pela tabela de distribuição abaixo, é possível observar que em quase 80% dos casos a heurística apresentada é capaz de obter soluções apenas 2,5% piores em relação ao caminho ótimo:
Comentários: Analisando os dados apresentados acima, é possível perceber que,
no caso esperado, a expectativa é de conseguir resultados bem melhores do que o limite
calculado de
(discutido na Questão (d)). Mesmo o pior caso que ocorreu é
muito distante deste limite. Os seguintes motivos podem ser apontados para este fato:
Resposta: O caminho mais curto encontrado é
, percorrendo uma distância de 36 unidades.
Resposta: Apresentada na Seção 2.