1. FAC ¸A
6.4 Subprogramas recursivos
6.4.1 Problema da Torre de Han´ oi
A solu¸c˜ao mecˆanica (humana) para a Torre de Han´oi ´e simples. Para uma torre comndiscos renomeamos os postes como sendo os postes origem,destino, e auxiliar, respectivamente como aqueles que contˆem a pilha original de discos, que conter˜ao a pilha final e que ser˜ao usados temporariamente na movimenta¸c˜ao. Como o problema de movimenta¸c˜ao ´e, na rea- lidade, um problema de contagem segundo o binˆomio de Newton, sabe-se de antem˜ao que o n´umero m´ınimo de movimentos ´e igual a 2n−1 para n discos. Sabemos ainda que para um n´umero par de discos, o primeiro movimento deve levar o disco 1 do poste origem para
6.4. Subprogramas recursivos 86 o poste auxiliar. Da mesma forma, se temos um n´umero ´ımpar de discos devemos levar inicialmente o disco 1 para o postedestino. Os movimentos seguintes tamb´em seguem uma ordem estrita de posi¸c˜oes, procurando fazer com que a sequˆencia deles leve at´e a solu¸c˜ao do problema.
No diagrama da figura 6.12 estamos considerando o poste 1 como origem, o poste 2 como auxiliar e o poste 3 como destino. Os pr´oximos cinco passos, considerando a posi¸c˜ao disposta naquela figura, seriam levar o disco 2 do poste origem para o destino, seguido de levar o disco 1 do auxiliar para o destino e o disco 3 do origem para o auxiliar, quando ent˜ao levamos o disco 1 do destino para o origem e, finalmente, o disco 2 do destino para o auxiliar, chegando na configura¸c˜ao apresentada na figura 6.13.
4 5 6
Poste 1 Poste 2 Poste 3
3 2 1
Figura 6.13: Torre de Han´oi ap´os seis movimentos.
O problema que aparece nesse momento ´e como escrever um programa que apresente, por exemplo, a sequˆencia de passos para movimentarn discos doPoste 1 para o Poste 3.
Como seria uma solu¸c˜ao iterativa para o problema?
A sa´ıda a ser apresentada pelo programa ´e trivial, consistindo basicamente de uma s´erie de mensagens do tipo “Levei o discokdo posteipara o postej”. O primeiro movimento tamb´em
´e trivial, bastando saber se o total de discos ´e par ou ´ımpar. As coisas come¸cam a complicar a partir do segundo movimento, quando temos que saber qual disco iremos movimentar e entre que postes isso ser´a feito a partir dos movimentos realizados anteriormente.
Um algoritmo para isso considera a paridade do disco movido no passo anterior, resul- tando nas seguintes op¸c˜oes para o pr´oximo movimento:
1. Se o disco movido era par, ent˜ao mova um disco do poste n˜ao utilizado nesse movimento e o coloque sobre esse disco;
2. Se o disco movido era ´ımpar, ent˜ao o movimento n˜ao envolver´a o poste em que foi colocado, levando um disco de um dos outros postes para o outro, de forma a n˜ao colocar um disco maior sobre outro menor.
Esse procedimento prossegue at´e que todos os discos estejam sobre o poste destino.
A solu¸c˜ao iterativa necessita que mantenhamos um registro de quais postes foram usados, quais discos foram movimentados e quantos e quais discos est˜ao em cada poste. Isso apa- rentemente n˜ao ´e t˜ao complexo e pode ser feito em poucas linhas de c´odigo. O programa da figura 6.14 mostra uma poss´ıvel solu¸c˜ao iterativa. Observe-se que essa solu¸c˜ao est´a bastante otimizada e que, com isso, fica bastante confusa quanto aos ´ındices dos vetores que controlam os postes (to, fr, sp). Outra otimiza¸c˜ao nesse programa diz respeito `a macro ALLO, que
6.4. Subprogramas recursivos 87 faz a aloca¸c˜ao de posi¸c˜oes para os postes, na quantidade de discos a serem usados. Como estruturas dinˆamicas est˜ao fora do contexto desse material, acreditemos nesse momento que as chamadas para ALLO apenas “definem” os vetores com os tamanhos corretos para o n´umero de discos usados.
#include <stdio.h>
#define ALLO(x) { x = (int *)malloc((n+3) * sizeof(int)); } main(int argc, char *argv[])
{ int i, *a, *b, *c, *p, *fr, *to, *sp, n, n1, n2;
n = atoi(argv[1]);
n1 = n+1; n2 = n+2;
ALLO(a); ALLO(b); ALLO(c); // cria os vetores a, b e c a[0] = 1; b[0] = c[0] = n1;
a[n1] = b[n1] = c[n1] = n1;
a[n2] = 1; b[n2] = 2; c[n2] = 3;
for(i=1; i<n1; i++) { // coloca os discos no vetor a a[i] = i; b[i] = c[i] = 0;
}
fr = a; // diz que o vetor a e’ o poste de origem if(n&1) { to = c; sp = b; } // verifica se n e’ par
else { to = b; sp = c; } while(c[0]>1) {
printf("move disc %d from %d to %d\n", i=fr[fr[0]++], fr[n2], to[n2]);
p=sp;
if((to[--to[0]] = i)&1) { // testa se o disco a ser retirado e’ par sp=to;
if(fr[fr[0]] > p[p[0]]) { to=fr; fr=p; } // testa o tipo de
else to=p; // movimento a fazer
}
else { sp=fr; fr=p; } }
}
Figura 6.14: Torre de Han´oi iterativa
Embora existam solu¸c˜oes iterativas eficientes, percebe-se que o racioc´ınio por tr´as delas
´e um tanto quanto confuso. Uma solu¸c˜ao recursiva para o mesmo problema ´e fundamental- mente elegante e n˜ao necessita de nenhuma das informa¸c˜oes indicadas no par´agrafo anterior ou no c´odigo da figura 6.14.
A solu¸c˜ao recursiva para esse problema parte da identifica¸c˜ao de que a solu¸c˜ao do pro- blema quando temos apenas um disco ´e trivial, consistindo apenas na movimenta¸c˜ao deste disco do poste origem para o poste destino. Observando-se ent˜ao que a solu¸c˜ao para uma quantidadenqualquer de discos pode ser obtida considerando primeiro que temos que mover
6.4. Subprogramas recursivos 88 n−1 discos do poste origem para o poste auxiliar, chegando ent˜ao ao problema trivial de um disco, e depois movimentando os mesmosn−1 discos do poste auxiliar para o destino, podemos chegar `a seguinte solu¸c˜ao recursiva:
torre=
se1disco −→ mover do poste atual para seu destino
resolver torre com n−1discos do poste atual para auxiliar se n discos −→ resolver torre com um disco
resolver torre com n−1discos do auxiliar para destino Esse pseudo-algoritmo leva, com alguma facilidade, ao programa recursivo para a Torre de Han´oi visto na figura 6.15.
#include <stdio.h>
hanoi(int n, int o, int a, int d) {
if (n == 1) /* se apenas um disco a solucao e’ trivial */
/*Passo 0*/ printf ("move disco %d de %d para %d\n",n,o,d);
else /* senao adota a solucao recursiva */
/*Passo 1*/ { hanoi(n-1, o,d,a);
/*Passo 2*/ printf ("move disco %d de %d para %d\n",n,o,d);
/*Passo 3*/ hanoi(n-1, a,o,d);
} /*Passo 4*/
}
main(int argc, char *argv[])
{int n; /* n e’ o numero inicial de discos na torre */
n = atoi(argv[1]); /* converte o parametro passado na linha de comando para um inteiro */
hanoi(n,1,2,3); /* chama a funcao para mudar n discos do poste 1 para o poste 3 */
}
Figura 6.15: Torre de Han´oi recursiva
Observando a solu¸c˜ao recursiva fica evidente que ela ´e mais curta e elegante. Uma ´unica nota a ser feita ´e a de que o segundo passo da solu¸c˜ao parandiscos (com n >1) foi alterado para j´a conter imediatamente a movimenta¸c˜ao do disco e n˜ao uma nova chamada da fun¸c˜ao hanoi. Isso serve como uma economia no n´umero de chamadas da fun¸c˜ao, embora pudesse ser feita com uma nova chamada sem nenhum problema.
Embora a fun¸c˜ao hanoi seja bastante simples, ´e interessante fazermos um pequeno ras- treamento de seu funcionamento para, por exemplo, uma torre com 4 discos. A primeira metade dos movimentos ´e mostrada na figura 6.16, incluindo o conte´udo das vari´aveis da fun¸c˜ao hanoi a cada chamada e as mensagens impressas.
6.4. Subprogramas recursivos 89
Posicao no programa valor de variaveis Saida main, chamando hanoi n=4
hanoi(1), passo 1 n=4, o=1, a=2, d=3 hanoi(2), passo 1 n=3, o=1, a=3, d=2 hanoi(3), passo 1 n=2, o=1, a=2, d=3
hanoi(4), passo 0 n=1, o=1, a=3, d=2 move disco 1 de 1 para 2 hanoi(4), passo 4 n=1, o=1, a=3, d=2
hanoi(3), passo 2 n=2, o=1, a=2, d=3 move disco 2 de 1 para 3 hanoi(3), passo 3 n=2, o=1, a=2, d=3
hanoi(4a), passo 0 n=1, o=2, a=1, d=3 move disco 1 de 3 para 3 hanoi(4a), passo 4 n=1, o=2, a=1, d=3
hanoi(3), passo 4 n=2, o=1, a=2, d=3
hanoi(2), passo 2 n=3, o=1, a=3, d=2 move disco 3 de 1 para 2 hanoi(2), passo 3 n=3, o=1, a=3, d=2
hanoi(3a), passo 1 n=2, o=3, a=1, d=2
hanoi(4b), passo 0 n=1, o=3, a=2, d=1 move disco 1 de 3 para 1 hanoi(4b), passo 4 n=1, o=3, a=2, d=1
hanoi(3a), passo 2 n=2, o=3, a=1, d=2 move disco 2 de 3 para 2 hanoi(3a), passo 3 n=2, o=3, a=1, d=2
hanoi(4c), passo 0 n=1, o=1, a=3, d=2 move disco 1 de 1 para 2 hanoi(4c), passo 4 n=1, o=1, a=3, d=2
hanoi(3a), passo 4 n=2, o=3, a=1, d=2 hanoi(2), passo 4 n=3, o=1, a=3, d=2
hanoi(1), passo 2 n=4, o=1, a=2, d=3 move disco 4 de 1 para 3 hanoi(1), passo 3 n=4, o=1, a=2, d=3
...
Figura 6.16: Execu¸c˜ao da Torre de Han´oi para 4 discos