Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Vers ao 2000.1
Estas notas de aula n ao devem ser usadas como u nica fonte de estudo. O aluno deve ler outros livros dispon veis na literatura. Nenhuma parte destas notas pode ser reproduzida, qualquer que seja a forma ou o meio, sem a permiss ao dos autores. Os autores concedem a permiss ao expl cita para a utilizac a o e reproduc a o deste material no contexto do ensino de disciplinas regulares dos cursos de graduac a o sob a responsabilidade do Instituto de Computac a o da UNICAMP.
c Copyright 2000
Instituto de Computac a o UNICAMP Caixa Postal 6176 13083970 CampinasSP fkm,tomasz @ic.unicamp.br
ii
10 Recursividade
Dizemos que um objeto e dito ser recursivo se ele for denido em termos de si pr oprio. Este tipo de denic a o e muito usado na matem atica. Um exemplo disto e a func a o fatorial, que pode ser denido como: se e se #"$&% ! Existem muitos objetos que podem ser formulados de maneira recursiva. Este tipo de denic a o permite que possamos denir innitos objetos de maneira simples e compacta. Podemos inclusive denir algoritmos que s ao recursivos, i.e., que s ao denidos em termos do pr oprio algoritmo. Nesta sec a o veremos como poderemos usar esta poderosa t ecnica como estrat egia para o desenvolvimento de algoritmos.
10.1
Os algoritmos recursivos s ao principalmente usados quando a estrat egia de se resolver um problema pode ser feita de maneira recursiva ou quando os pr oprios dados j a s ao denidos de maneira recursiva. Naturalmente exitem problemas que apresentam estas duas condic o es mas que n ao se e aconselhado usar algoritmos recursivos. Um problema que pode ser resolvido de maneira recursiva tamb em pode ser resolvido de maneira interativa. Algumas das principais vantagens de usar recurs ao s ao a possibilidade de se gerar programas mais compactos, programas f aceis de se entender e o uso de uma estrat egia para se resolver o problema. Esta estrat egia e a de atacar um problema (projetando um algoritmo) sabendo (supondo) se resolver problemas menores: Projeto por Induc a a usamos esta id eia o. Note que j para desenvolver o algoritmo de busca bin aria. Os objetos de programac a o que iremos usar para trabalhar com a recurs ao ser ao as func o es e os procedimentos. Assim, uma rotina (func a o ou procedimento) e dita ser recursiva se ela chama a si mesma. Uma rotina recursiva pode a o ent ao ' e uma rotina ser de dois tipos: se uma rotina ' faz uma chamada de si mesmo no meio de sua descric recursiva direta; caso ' n ao fac a uma chamada a ela mesma, mas a outras rotinas que porventura podem levar a chamar a rotina ' novamente, ent ao ' e uma rotina recursiva indireta. Quando uma rotina recursiva est a sendo executada, podemos visualizar uma seq ue ncia de execuc o es, uma chamando a outra. Veja a gura 26.
Rotina R; Parmetros: Par1,...,ParP. (Instncia da rotina R) Variveis V1,V2,...,Vn. R {Chamada 1} Parmetros e Variveis locais: Par1,...,ParP,V1,...,Vn R {Chamada 2} Parmetros e Variveis locais: Par1,...,ParP,V1,...,Vn R {Chamada 3} Parm. e Variveis locais: Par1,...,ParP,V1,...,Vn R {Chamada 4} Parm. e Variveis locais: Par1,...,ParP,V1,...,Vn R {Chamada K} Par. e Var. locais: Par1,...,ParP,V1,...,Vn Instncia suficientemente pequena. Sem chamar recursivamente. Resoluo Direta.
e sua u ltima chamada recursiva. Figura 26: Seq ue ncia das chamadas recursivas de uma rotina ( at Quando em uma determinada chamada recursiva a rotina faz refer encia a uma vari avel ou par ametro, esta est a condicionada ao escopo daquela vari avel. Note que em cada uma das chamadas recursivas, as vari aveis e par ametros (de mesmo nome) podem ter valores diferentes. Se a rotina faz refer encia a ` vari avel ) na terceira chamada recursiva, esta ir a obter e atualizar o valor desta vari avel naquela chamada. A gura 27 apresenta a congurac a o da mem oria usada para a execuc a o do programa, chamada pilha de execuc a o, no momento em que foi feita a 0 - esima chamada recursiva. A seta indica o sentido do crescimento da pilha de execuc a o. 97
Configurao da Pilha de Execuo na Chamada K MC PAR VL MC PAR VL Chamada 1 Chamada 2 MC PAR VL Chamada K
MC = Memria de Controle (para retorno da chamada) PAR = Memria relativa aos parmetros da chamada VL = Memria relativa s variveis locais da chamada
esima chamada recursiva. Figura 27: Congurac a o da pilha de execuc a o ap os 0 -
10.2
Note que na gura 26, da primeira chamada at eau ltima, zemos 0 chamadas recursivas. Em um programa recursivo e muito importante se garantir que este processo seja nito. Caso contr ario, voc e ter a um programa que ca fazendo innitas chamadas recursivas (no caso at e que n ao haja mem oria suciente para a pr oxima chamada). Para garantir que este processo seja nito, tenha em mente que sua rotina recursiva trabalha sobre uma inst ancia e para cada chamada recursiva que ela ir a fazer, garanta que a inst ancia que ser a passada a ele seja sempre mais restrita que a da chamada superior. Al em disso, garanta que esta seq ue ncia de restric o es nos leve a uma inst ancia isto que sucientemente simples e que permita fazer o c alculo deste caso de maneira direta, sem uso de recurs ao. E garantir a que seu processo recursivo tem m. Vamos chamar esta inst ancia sucientemente simples (que a rotina recursiva resolve de maneira direta) de base da recurs ao. Nas duas guras a seguir apresentamos um exemplo de programa usando recurs ao direta e indireta. program ProgramaFatorialRecursivo; function fatorial(n : integer):integer; begin if (n 0) then fatorial : 1 else fatorial : n 1 fatorial(n 1); end; fatorial var n : integer; begin write(Entre com um n umero: ); readln(n); writeln(O fatorial de ,n, e igual a ,fatorial(n)); end. program ProgramaParesImpares; function impar(n : integer):boolean; forward; function par(n : integer):boolean; begin if n 0 then par: true else if n 1 then par: false else par : impar(n 1); end; par function impar(n : integer):boolean; begin if n 0 then impar : false else if n 1 then impar: true else impar : par(n 1); end; impar var n : integer; begin write(Entre com um inteiro positivo: ); readln(n); if (par(n)) then writeln(N umero ,n, e par.) else writeln(N umero ,n, e mpar. ); end. Figura 29: Func o es par e impar (recurs ao indireta).
Estes dois exemplos s ao exemplos fabricados de rotinas recursivas, mas que tem o prop osito de apresentar este tipo de rotina como uma primeira inst ancia. Naturalmente existem soluc o es melhores para se implementar as func o es acima. Discutiremos mais sobre isso posteriormente. Agora vamos analisar melhor a func a o fatorial recursiva. Note que o par ametro da func a o fatorial e um n umero 98
inteiro positivo . A cada chamada recursiva, o par ametro que e passado e diminuido de uma unidade (o valor que ancia do problema a ser resolvido (o c alculo de e passado na pr oxima chamada recursiva e 2 ! ). Assim, a inst fatorial) vai diminuindo (cando mais restrito) a cada chamada recursiva. E este processo deve ser nito porque existe uma condic a o que faz com que ele pare quando a inst ancia car sucientemente pequena: caso 3 . Considere o programa da gura 29. Neste programa apresentamos um exemplo de recurs ao indireta. A func a o par n ao chama a si mesma, mas chama a func a o impar que em seguida pode chamar a func a o par novamente. A func a o mpar). par (impar) retorna true caso o par ametro seja par ( Note que a primeira rotina que aparece no momento da compilac a o e a func a o par. Ela faz a chamada da func a o impar. Como esta func a o impar est a denida abaixo da func a o par, a compilac a o do programa daria erro (por n ao ter sido previamente denida), caso n ao coloc assemos uma informac a o dizendo a cara da func a o impar. Isto pode ser feito colocando antes da sua chamada uma diretiva dizendo como e a cara da func a o impar. Colocando apenas o cabec alho da func a o ou procedimento, seguido da palavra forward;, estaremos dizendo ao compilador que tipo de par ametros ela aceita e que tipo de resultado ela retorna. Com isso, poderemos fazer chamadas da rotina impar, mesmo que sua especicac a o seja feita bem depois destas chamadas. Note tamb em que o tamanho das inst ancias das chamadas recursivas das func o es par e impar tamb em diminui de tamanho.
10.3
Torres de Hanoi
Torre de Hanoi: H a um conjunto de 3 pinos: 1,2,3. Um deles tem discos de tamanhos diferentes, sendo que os maiores est ao embaixo dos menores. Os outros dois pinos est ao vazios. O objetivo e mover todos os discos do primeiro pino para o terceiro pino, podendo se usar o segundo como pino auxiliar. As regras para resolver o problema s ao. 1. Somente um disco pode ser movido de cada vez. 2. Nenhum disco pode ser colocado sobre um disco menor que ele. 3. Observando-se a regra 2, qualquer disco pode ser movido para qualquer pino. Escreva a ordem dos movimentos para se resolver o problema.
Vamos ver um exemplo de programa recursivo, usando projeto indutivo, para se resolver um problema, chamado Torres de Hanoi.
Figura 30: Torres de Hanoi com 4 discos. Note que este problema consiste em passar os discos de um pino de origem, para um pino destino, usando mais um pino auxiliar. Para resolver o problema das torres de hanoi com discos, podemos usar a seguinte estrat egia (veja gura 31): 1. Passar os
3 !
2. Passar o maior disco para o terceiro pino. 3. Passar os 3 ! discos do segundo pino para o terceiro pino.
Figura 31: Estrat egia do algoritmo das Torres de Hanoi. Note que no item 1 acima, passamos # ! discos de um pino para o outro. Aqui e importante observar que # todos estes ao menores que o maior disco que cou no pino. Assim, podemos trabalhar com estes ! discos s ! discos como se n ao existisse o disco grande. Pois mesmo que algum destes ! discos que em cima do disco maior, isto n ao ser a nenhum problema, j a que todos os disco em cima do maior disco s ao menores que este. Este 3 mesmo racioc nio se propaga para se mover os ! discos em etapas. Observe que resolvemos o problema maior, com discos, sabendo se resolver um problema de 4 discos. Al em disso, para uma inst ancia sucientemente pequena, com discos, resolvemos o problema de maneira direta (base da recurs ao). program ProgramaHanoi; procedure Hanoi(n,origem,auxiliar,destino : integer); begin if n " 0 then begin hanoi(n 1,origem,destino,auxiliar); writeln(Mova de ,origem, para ,destino); hanoi(n 1,auxiliar,origem,destino); end; end; var n : integer; begin write(Entre com a quantidade de discos no primeiro pino: ); readln(n); writeln(A seq u encia de movimentos para mover do pino 1 para o pino 3 e:); hanoi(n,1,2,3); end.
10.4
H a momentos em que mesmo que o c alculo seja denido de forma recursiva, devemos evitar o uso de recurs ao. Veremos dois casos onde o uso de recurs ao deve ser evitado. Chamada recursiva no in cio ou m da rotina Quando temos apenas uma chamada que ocorre logo no in cio ou no m da rotina, podemos transformar a rotina em outra interativa (sem recurs ao) usando um loop. De fato, nestes casos a rotina recursiva simplesmente est a simulando 100
uma rotina interativa que usa uma estrutura de repetic a o. Este e o caso da func a o fatorial que vimos anteriormente e que pode ser transformado em uma func a o n ao recursiva como no exemplo 7.9. O motivo de preferirmos a vers ao n ao recursiva e porque cada chamada recursiva aloca mem oria para as vari aveis 5 e par locais ametros (al em da mem oria de controle) como ilustrado na gura 27. Assim, a func a o fatorial recursiva chega a gastar uma mem oria que e proporcional ao valor dado como par ametro da func a o. Por outro lado, a func a o fatorial interativa do exemplo 7.9 gasta uma quantidade pequena e constante de mem oria local. Assim, a vers ao interativa e mais r apida e usa menos mem oria para sua execuc a o. A seguir apresentamos um outro exemplo onde isto ocorre. Exemplo 10.1 A func a aria usa uma estrat egia tipicamente recursiva com uso de projeto por o para fazer a busca bin induc a eia da estrat egia e o. A rotina deve encontrar um determinado elemento em um vetor ordenado. A id tomar um elemento do meio de um vetor ordenado e compar a-lo com o elemento procurado. Caso seja o pr oprio elemento, a func a a ario, a func a o retorna a posic o deste elemento. Caso contr o continua sua busca da mesma maneira em uma das metades do vetor. Certamente a busca na metade do vetor pode ser feita de maneira recursiva, como apresentamos na gura 32. Na gura 33 apresentamos a vers ao interativa. Note que a base da recurs ao foi o vetor vazio que certamente e um vetor sucientemente pequeno para resolvermos o const MAX type TipoVet
function BuscaBin(var v : TipoVet; inicio,m : integer; x : real):integer; var meio: integer; begin if (inicio " m) then vetor vazio BuscaBinaria : 0 nao achou else begin meio : (inicio6 m) div 2; if (x 7 v[meio]) then BuscaBin : BuscaBin(v,inicio,meio 1,x) else if (x " v[meio]) then BuscaBin : BuscaBin(v,meio 6 1,m,x) else BuscaBin : meio; end ; end; BuscaBin Figura 32: Busca bin aria recursivo. problema de maneira direta. o de processamento Repetic a
Quando fazemos uso de recurs ao com v arias chamadas recursivas ocorre na maioria das vezes que cada uma destas chamadas recursivas e independente uma da outra. Caso ocorram os mesmos c alculos entre duas chamadas recursivas independentes, estes c alculos ser ao repetidos, uma para cada chamada. Este tipo de comportamento pode provocar uma quantidade de c alculos repetidos muito grande e muitas vezes torna o programa invi avel. Um exemplo claro disto ocorre na func a o Fibonacci, que apesar de ter sua denic a o de maneira recursiva, o uso de uma func a o recursiva causa um n umero exponencial de c alculos, enquanto a vers ao interativa pode ser feito em tempo proporcional a (o par ametro da func a o). 8@9BADCFEHGPIQIR9
se e"
%
A func a o Fibonacci recursiva, apresentada a seguir, e praticamente a traduc a o de sua denic a o para Pascal. function bo(n : integer):integer; begin if n 0 then bo: 0 else if n 1 then bo: 1 else bo: bo(n 1) 6 bo(n 2); end; Na gura 34, apresentamos as diversas chamadas recursivas da func a o bonacci, descrita acima, com #gf . Por simplicidade, chamamos a func a o Fibonacci de h .
F(5) := F(4) := F(3) := F(2) := F(1):=1 F(2) := F(3) := F(2) :=
+ F(0):=0
F(1):=1
F(1):=1
+ F(0):=0
F(1):=1
+ F(0):=0
+ F(1):=1
F(2):=1+0=1
F(2):=1+0=1 F(3):=1+1=2
Figura 34: Chamadas recursivas feitas por Fibonacci(5). Note que cada chamada recursiva e desenvolvida independente das chamadas recursivas anteriores. Isto provoca uma repetic a o dos c alculos para muitas das chamadas. Por exemplo: quando foi feita a chamada h Bi (mais a direita na gura 34) para o c alculo de h Bf , poder amos ter aproveitado o c alculo de h Bi que tinha sido feito anteriormente para p . Este tipo de duplicac a o de chamadas ocorre diversas vezes. Neste pequeno exemplo j ae poss vel se calcular h ver que a chamada de h ! ocorreu 5 vezes. De fato, uma an alise mais detalhada mostraria que a quantidade de processamento feito por esta func a o e exponencial em (com base um pouco menor que 2) enquanto o processamento feito pela correspondente func a o interativa e linear em .
102
s 0ut
07
s 3 s 6 3 0 t 0 twv
. Usando a identidade
fac a uma func a o recursiva que calcula xy . Qual a relac a angulo de Pascal ? Fac a uma func a o acima com o tri o que calcula o valor de estrat egia do c alculo do tri angulo de Pascal.
xy!
aproveitando a
Exerc cio 10.2 1. Uma planilha linear e ndices de 1 a MaxInd=100) onde cada formada por um vetor (com elemento e elula. chamado de c 2. Cada c elula da planilha pode conter um valor real ou uma f ormula simples. Para diferenciar estas duas formas, cada c elula apresenta um campo chamado Tipo que pode ser um dos seguintes caracteres: (n,+,-,*,/). 3. Caso a c elula tenha Tipo igual a n, a c elula cont em um outro campo chamado valor que armazena um n umero real. 4. Se Tipo tem o caracter + (resp. -,*,/), ent ao h a dois campos (Ind1 e Ind2) que cont em ndices do vetor e indicam que o valor deste elemento e a a ao) dos valores associados a soma (resp. subtrac o, multiplicac o e divis a elulas dos ndices Ind1 e Ind2. ` s c Por exemplo, a f ormula simples + 4 5 indica que o valor desta c elula e elulas 4 e 5. obtido somando-se os valores das c 5. Para uma ilustrac a o desta planilha, veja a gura seguinte:
103
9. Para manipular os dados nesta planilha, voc e dever a fazer um programa para ler uma seq ue ncia de linhas que pode ter um dos seguintes formatos: Linha lida Explicac a o do comando n [Ind] [Val] Coloca na c elula [Ind] o valor [Val]. + [Ind] [Ind1] [Ind2] O valor da c elula [Ind] e elula [Ind1] somado ao o valor da c valor da c elula [Ind2]. O valor da c elula [Ind] e elula [Ind1] subtraido do o valor da c valor da c elula [Ind2]. O valor da c elula [Ind] e elula [Ind1] multiplicado o valor da c com o valor da c elula [Ind2]. O valor da c elula [Ind] e elula [Ind1] dividido pelo o valor da c valor da c elula [Ind2]. Imprime na tela o valor da c elula [Ind]. Este comando n ao altera a planilha. Este comando (.) naliza o programa.
[Ind]
[Ind1]
[Ind2]
[Ind]
[Ind1]
[Ind2]
[Ind]
[Ind1]
[Ind2]
[Ind]
10. Exemplo: Vamos considerar que queremos avaliar o valor da f ormula 6w 1 ` 6wh , para diferentes valores de h . Podemos considerar o valor de na c elula 1, o valor de na c elula 2, v v v v v ..., o valor de h na c elula 6. Com isso, podemos montar nossa express ao com os seguintes comandos: Linhas de entrada Coment arios e n 1 10 de d F d d dF n 2 20 iF n 3 30 i d iF p P p pP n 4 40 d fF f F f d n 5 50 n 6 60 gf d f h f d 6 ih d 6j + 7 1 2 ih d 8 3 4 gk d i p gk d ` h f 6gf gl d 6jh + 9 5 6 gl d 10 7 8 d ih 1mgk n 6 1 `o# / 11 10 9 bd gl bn 6 1 `# 6h Neste ponto a planilha apresenta a congurac a o da gura 35 p 11 Imprime o c alculo de Imprime -2.73 n n p . 1 5 11 1 5
6 dF 1 BiF4epP BfF 6f de f d f Imprime o c alculo de 6 dF 1 BiFpepP Bf 6qf
Fim do processamento
Imprime -3.23
e f
Exerc cio adicional 1: Considere agora que cada c elula pode ser referenciada mais de uma vez, mas a obtenc a o do valor de cada c elula n ao deve fazer uso dela mesma. Caso isto ocorra, dizemos que a planilha est a inconsistente. Fac a uma rotina que verica se uma planilha est a inconsistente. Exerc cio adicional 2: Considere agora uma planilha bidimensional, conde cada c elula deve ser especicada por dois ndices. Fac a o mesmo programa, com as devidas modicac o es, para trabalhar com este tipo de planilha.
104
10.5
Exerc cios
1. Fac a uma func a o recursiva para encontrar um elemento em um vetor. A rotina deve ter como par ametros: o vetor, o n umero de elementos do vetor (o primeiro elemento do vetor comec a no ndice 1), e um valor a ser procurado. A rotina retorna o ndice do elemento no vetor, caso este se encontre no vetor, -1 caso contr ario. 2. Fac a uma rotina recursiva para imprimir os elementos de um vetor, na ordem do menor ndice primeiro. 3. Fac a uma rotina recursiva para imprimir os elementos de um vetor, na ordem do maior ndice primeiro. 4. A func a o de Acherman e denida recursivamente nos n umeros n ao negativos como segue:
Fac a um procedimento recursivo para computar a func a o de Ackerman. Obs.: Esta func a o cresce muito r apido, assim ela deve poder ser impressa para valores pequenos de t e . 5. Fac a uma rotina recursiva para imprimir todas as permutac o es dos n umeros de a , uma permutac a o por linha. 6. Dado uma cadeia de caracteres de comprimento , escreva uma rotina recursiva que inverte a seq ue ncia dos caracteres. I.e., o primeiro caracter ser aou ltimo e o u ltimo ser a o primeiro. 7. Um vetor tem d valores inteiros (gura (a)), onde 0 e um inteiro positivo, 03z . Este vetor representa uma gura hier arquica (gura (b)) da seguinte maneira: D
1 2 3 4 5 6 7
A B C D E F G A
(a)
B C
(b)
F E G
Voc e pode imaginar que este vetor est a representando uma a rvore geneal ogica de 3 n veis. Infelizmente, o usu ario do programa que faz uso deste vetor necessita de algo mais amig avel para ver esta estrutura. Fac a uma rotina recursiva que dado este vetor { e o valor 0 , imprime as seguintes linhas: G----------F---------------E----------D--------------------C----------B---------------A----------Note que ca bem mais f acil para enxergar a hierarquia visualizando este desenho. A profundiade que e impresso cada elemento e feita controlando os espac os denidos para a profundidade de elemento na hierarquia. 8. Escreva uma func a o recursiva para calcular o m aximo divisor comum, mdc, de dois inteiros positivos da seguinte |~} maneira: se q e mod & |~} v wt3b se 7 |~} v wt3b sv |2n} caso contr ario.
9. C alculo de determinantes por co-fatores. uma matriz quadrada de ordem . O Menor Complementar # i i , de um elemento r da matriz e denido como o determinante da matriz quadrada de ordem # ! i obtida a partir da matriz , excluindo os elementos da linha e da coluna . O Co-Fator de e denido como:
sv
Seja
i 2g % ! O determinante de uma matriz quadrada de ordem pode ser calculado usando os co-fatores da linha
seguinte maneira: det
da
6 6 RR 6 y y % q
105
O mesmo c alculo pode ser feito pelos co-fatores da coluna da seguinte maneira: det Fac a uma rotina recursiva para calcular o determinante de uma matriz de ordem usando o m etodo descrito acima, onde a rotina tem o seguinte cabec alho: function determinante(var :TipoMatrizReal; n:integer):real; onde TipoMatrizReal e um tipo adequado para denir matriz e e a ordem da matriz. Obs.: Existem na literatura outros m etodos mais ecientes para se calcular o determinante. %R%R% %R%R% R % R % % 10. Seja { gitos entre e l . Fac a uma rotina recursiva para vericar { { {P { y um vetor com d v v v v v v se os elementos { %R%R% {P formam um n umero pal ndrome, q qqg . Obs.: Pode considerar que os v ar v com alguns d n umeros podem comec gitos s, assim, o n umero 0012100 e pal ndrome.
` ` 6 6 RR 6q y y % j
106