Torres de Hanoi Estrutura de Dados Marco Antonio Montebello Júnior [email protected] Torres de Hanoi “No século XIX, um jogo chamado Torres de Hanoi apareceu na Europa, junto com um material promocional explicando que o jogo representava uma tarefa conduzida no Templo de Brahma (montanhas de Hanoi – sacerdotes brâmanes). Dizia, ainda, que na criação do mundo, aos sacerdotes foi dada uma plataforma de metal na qual existiam três hastes de diamante. Estrutura de Dados Torres de Hanoi Na primeira haste existiam 64 discos de ouro, cada um ligeiramente menor em relação ao que estava por cima. (A versão mais exótica vendida na época na Europa tinha 8 (oito) discos e 3 (três) hastes de madeira). Estrutura de Dados Torres de Hanoi Os sacerdotes foram incumbidos com a tarefa de mover todos os discos de ouro do primeiro disco para o terceiro, porém, com as seguintes condições: a. Mover apenas um disco por vez; b. E nenhum disco poderia ficar sobre um disco menor. Estrutura de Dados Torres de Hanoi Entretanto, poderiam utilizar qualquer haste de diamante como descanso para os discos. Aos sacerdotes foi dito que quando eles terminassem de movimentar os 64 discos de ouro, significaria o fim do mundo!!!” Estrutura de Dados Torres de Hanoi Estrutura de Dados Desenvolvendo uma solução para Torres de Hanoi Concentrar nossa atenção não no primeiro passo Mas sim, no passo mais difícil: mover o último disco Como acessar o disco número 64? Lembre-se que somente um disco pode ser movido por vez E o último (o maior) não pode nunca estar sobre os demais Quando movemos o disco 1, não pode existir nenhum outro disco nas torres 1 e 3 Estrutura de Dados Resumindo os passos do algoritmo de Torres de Hanoi Move (63, 1, 2, 3) Move 63 discos da torre 1 para 2 (torre 3 temporária) Escreva: “Mova o disco número 64 da torre 1 para torre 3” Move (63, 2, 3, 1) Move 63 discos da torre 2 para torre 3 (torre 1 temporária) Estrutura de Dados Refinamento Para escrever o algoritmo na sua forma final, nós devemos saber em cada passo, qual torre será utilizada como armazenamento temporário, e assim, nós construímos a função “move” com as seguintes especificações: void move(int qtd, int inicio, int fim, int aux) Pré-condição: Existe no mínimo qtd discos na torre inicio. O disco de topo (se existir) nas torres aux e fim, é maior do que qualquer outros qtd discos presentes na torre inicio. Pós-Condições: Os qtd discos que estavam na torre inicio foram movidos para a torre fim. A torre temp (usada como armazenamento temporário) retornou para sua posição inicial. Estrutura de Dados Refinamento Nossa tarefa deve terminar num número finito de passos. Uma regra óbvia é que, quando não existir mais discos para ser movido, não há mais nada para fazer... Estrutura de Dados Analisando um programa que considera as regras anteriores #define discos = 64; //faça essa constante ser menor para testar o programa /* Pré: Nenhum Pós: A simulação das torres de Hanoi terminou */ void move(int qtd, int inicio, int fim, int aux); main() { move (discos, 1, 3, 2); } void move(int qtd, int inicio, int fim, int aux) { if(qtd > 0) { move(qtd – 1, inicio, aux, fim); printf(“Mova disco %i de %i para %i”, qtd, inicio, fim); move(qtd - 1, aux, fim, inicio); } } Estrutura de Dados Acompanhamento / Análise Acompanhamento e análise do programa recursivo de Torres de Hanói com árvore de recursão. Demonstração: move(3, 1, 3, 2); Estrutura de Dados Analisando a árvore de recursão Uma instrução é exibida para cada vértice (ou nó) Exceto para as folhas que são chamadas com count == 0 O número de vértices tipo não-folhas é: 1 + 2 + 4 +...+ 263 = 20 + 21 + 22+ . . . + 263 = 264 –1 Estrutura de Dados Podemos concluir o seguinte Número de movimentos para movimentar todos 64 discos é de: 264 -1 Este valor pode ser estimado por: Número aproximado de movimentos é: 103 = 1000 1024 = 210 264 = 24 x 260 24 x 1018 = 1,6x1019 Existem aproximadamente 3,2x107 segundos num ano Suponha instruções num ritmo de um movimento por segundo A tarefa total levará quase 5x1011 anos Estimativa de vida do universo: mínimo 20 bilhões (2x1010) anos Duração do mundo: 25 vezes mais do que já tem!!! Estrutura de Dados Consideração de tempo (execução) e espaço (memória) do algoritmo recursivo de Torres de Hanoi Não existiria um computador que rodaria o programa de Torres de Hanoi Ele falharia por falta de tempo Mas certamente não por falta de espaço O espaço necessário é somente para fazer o controle das 64 chamadas recursivas Mas o tempo necessário requerido, é aquele para fazer 264 cálculos Estrutura de Dados Desenvolvendo algoritmos recursivos Ache o passo chave Ache uma regra de parada Como este problema pode ser dividido? Como posso desenvolver o passo chave da solução? Uma regra de parada indica que o problema ou uma parte razoável do mesmo foi feito Ela é geralmente pequena e dá a solução trivial, sem a necessidade da recursão Monte seu algoritmo Combine a regra de parada e o “passo chave” usando uma instrução if Estrutura de Dados Desenvolvendo algoritmos recursivos Verifique o término Certificar que a recursão sempre terminará Comece com uma situação geral Verifique se num número finito de passos, a regra de parada será satisfeita e a recursão termina Desenhe uma árvore de recursão É a ferramenta chave para analisar algoritmos recursivos Estrutura de Dados Tipos de recursividade Recursão de Cauda Essa situação na qual a chamada recursiva é o último comando da função, é denominada recursão de cauda Isso forma um “looping” que será repetido até que uma condição de parada seja satisfeita A recursão de cauda pode sofrer transformações até ser obtida uma estrutura de interação, quando observaremos ganhos em tempo e espaço Estrutura de Dados Exemplo Fatorial /* Fatorial : versão recursiva Pós : n é um inteiro não negativo Pré : retorna o valor do fatorial de n */ int fatorial(int n) { if (n == 0) return 1; else return n*fatorial(n-1); } /* fatorial : versão interativa Pós : n é um inteiro não negativo Pré : retorna o valor do fatorial de n */ int fatorial(int n) { int count, product = 1; for(count = 1; count < = n; count ++) product*= count; return product; } Estrutura de Dados Considerando os exemplos anteriores Qual dos programas usa menos espaço de memória? Acompanhe a execução recursiva para n = 5 fatorial(5) = 5*fatorial(4) = 5*(4*fatorial(3)) = 5*(4*(3*fatoriall(2))) = 5*(4*(3*(2*fatorial(1)))) = 5*(4*(3*(2*(1*(fatorial(0)))))) = 5*(4*(3*(2*(1*)))) = 5*(4*(3*(2*1))) = 5*(4*(3*2)) = 5*(4*6) = 5*24 = 120 Estrutura de Dados Diretrizes e conclusões gerais A árvore de recursão é uma boa possibilidade para ajudar a decisão entre utilizar um algoritmo recursivo e não recursivo Se ela tiver uma forma simples, a versão interativa pode ser melhor Se ela envolver tarefas de duplicação, outras estruturas que não sejam pilhas poderão ser mais apropriadas, então a necessidade de recursão pode desaparecer Se a árvore de recursão parece ser consistente, com poucas tarefas de duplicação, então a recursão pode ser o método mais natural Estrutura de Dados Ainda mais... A recursão é considerada uma abordagem topdown para resolver um problema A interação está mais para um abordagem bottom-up É sempre verdadeiro que a recursão pode ser substituída pela interação e pilhas É verdade também, por outro lado, que qualquer programa interativo que manipula uma pilha pode ser substituído por um programa recursivo sem nenhuma pilha Estrutura de Dados Mensagem sobre a utilização da recursão “Um programador cuidadoso deve não somente perguntar, se a recursão deve ser removida, mas também perguntar, quando um programa usa pilhas, se a introdução da recursão poderia produzir um programa mais natural e de melhor entendimento, agregando melhorias na abordagem e nos resultados do problema” Estrutura de Dados