In [1]:
# Ignore esse código inicial, é apenas para preparar a página!
from IPython.display import YouTubeVideo, HTML, Image, display
css_file = 'http://professor.ufabc.edu.br/~fernando.teubl/resources/css/ipython-notebook.css'
#HTML(open(css_file, "r").read())
import requests; HTML(requests.get(css_file).text)


Out[1]:

Introdução à Programação em Python

Recursão

Em um programa é muito comum chamarmos uma função dentro de uma outra função.


In [2]:
def imprime(i):
    print (i)

def imprimeLista(l):
    for e in l:
        imprime (e)

imprimeLista([1, 3, 5, 7])


1
3
5
7

Entretanto, nada impede de uma função chamar ela mesma!

Como exemplo, vamos pensar na função fatorial. O fatorial de 3 e 6 respectivamente pode ser calculado conforme descrito abaixo:

$3! = 3 \times 2 \times 1 = 6$

$6! = 6 \times 5 \times 4 \times 3 \times 2 \times 1 = 720$

De forma genérica, podemos calcular o fatorial como:

$n! = n \times (n-1) \times (n-2) \times \ldots \times 3 \times 2 \times 1$

Deste modo, podemos facilmente implementar uma função para calcular o fatorial:


In [3]:
def fatorial(n):
    fat = 1
    while n > 1:
        fat *= n
        n -= 1
    return fat

print(fatorial(3))
print(fatorial(6))


6
720

Uma outra forma de cálcular o fatorial é:

$3! = 3 \times 2! = 3 \times 2 = 6$

$6! = 6 \times 5! = 6 \times 120 = 720$

De modo genérico, temos:

$n! = n \times (n-1)!$

Pela equação acima, podemos deduzir que qualquer fotorial pode ser calculado como o seu valor multiplicado pelo fatorial do seu valor subtraído por um.

E quanto vale o fatorial de $n-1$?

R: $(n-1)! = (n-1) \times (n-2)!$

Vamos propor uma função que tenta calcular a equação acima:

def fatorial(n)
    return n * (n-1)!

Entretanto, como podemos calcular o (n-1)!? A função fatorial(n) que estamos desenvolvendo não tem este objetivo? Então, por que não utilizar esta própria função para calcular o fatorial de (n-1)? Neste sentido, teremos:

def fatorial(n)
    return n * fatorial(n-1)

Entretanto, existe um erro grave nesta função: se a função chama ela mesma, quando esta função termina e retorna?

R: Da forma que está, não termina... :'(


In [0]:
import sys
sys.setrecursionlimit(50)
# Ao executar esta função, o python ficará processando até
# ocorrer um estouro de pilha de memória (stack overflow).
def fatorial(n):
    return n * fatorial(n-1)

print(fatorial(6))

sys.setrecursionlimit(1000)


Traceback (most recent call last):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 3066, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "<ipython-input-1-176658a242a5>", line 8, in <module>
    print(fatorial(6))

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

  File "<ipython-input-1-176658a242a5>", line 6, in fatorial
    return n * fatorial(n-1)

RecursionError: maximum recursion depth exceeded


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 1877, in showtraceback
    stb = value._render_traceback_()

AttributeError: 'RecursionError' object has no attribute '_render_traceback_'


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 3006, in run_ast_nodes
    if self.run_code(code, result):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 3083, in run_code
    self.showtraceback()

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 1880, in showtraceback
    value, tb, tb_offset=tb_offset)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 1242, in structured_traceback
    self, etype, value, tb, tb_offset, number_of_lines_of_context)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 1150, in structured_traceback
    self, etype, value, tb, tb_offset, number_of_lines_of_context

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 1002, in structured_traceback
    tb_offset)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 951, in format_exception_as_a_whole
    frames = self.format_records(records)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 796, in format_records
    for token_type, token, start, end, line in generate_tokens(linereader):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/tokenize.py", line 601, in _tokenize
    pseudomatch = _compile(PseudoToken).match(line, pos)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/tokenize.py", line 173, in _compile
    return re.compile(expr, re.UNICODE)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/re.py", line 224, in compile
    return _compile(pattern, flags)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/re.py", line 293, in _compile
    p = sre_compile.compile(pattern, flags)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_compile.py", line 536, in compile
    p = sre_parse.parse(p, flags)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 829, in parse
    p = _parse_sub(source, pattern, 0)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 633, in _parse
    item = subpattern[-1:]

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 159, in __getitem__
    return SubPattern(self.pattern, self.data[index])

RecursionError: maximum recursion depth exceeded while calling a Python object


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 1877, in showtraceback
    stb = value._render_traceback_()

AttributeError: 'RecursionError' object has no attribute '_render_traceback_'


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/ipykernel/ipkernel.py", line 175, in do_execute
    shell.run_cell(code, store_history=store_history, silent=silent)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 2902, in run_cell
    interactivity=interactivity, compiler=compiler, result=result)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 3031, in run_ast_nodes
    self.showtraceback()

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 1880, in showtraceback
    value, tb, tb_offset=tb_offset)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 1242, in structured_traceback
    self, etype, value, tb, tb_offset, number_of_lines_of_context)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 1150, in structured_traceback
    self, etype, value, tb, tb_offset, number_of_lines_of_context

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 1002, in structured_traceback
    tb_offset)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 951, in format_exception_as_a_whole
    frames = self.format_records(records)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/site-packages/IPython/core/ultratb.py", line 796, in format_records
    for token_type, token, start, end, line in generate_tokens(linereader):

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/tokenize.py", line 601, in _tokenize
    pseudomatch = _compile(PseudoToken).match(line, pos)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/tokenize.py", line 173, in _compile
    return re.compile(expr, re.UNICODE)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/re.py", line 224, in compile
    return _compile(pattern, flags)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/re.py", line 293, in _compile
    p = sre_compile.compile(pattern, flags)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_compile.py", line 536, in compile
    p = sre_parse.parse(p, flags)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 829, in parse
    p = _parse_sub(source, pattern, 0)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 778, in _parse
    p = _parse_sub(source, state)

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 437, in _parse_sub
    itemsappend(_parse(source, state))

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 633, in _parse
    item = subpattern[-1:]

  File "/home/olivetti/anaconda/envs/py3/lib/python3.5/sre_parse.py", line 159, in __getitem__
    return SubPattern(self.pattern, self.data[index])

RecursionError: maximum recursion depth exceeded

O principal problema do exemplo anterior é que a função não sabe quando terminar!

Deste modo, toda função recursiva precisa ter uma condição de término.

Toda função recursiva deve ter uma lógica similar ao demonstrado a seguir:

def funcRecursiva():
    # Parte TRIVIAL
    if <conhecemos_a_resposta>:
        return <resposta>

    #parte GERAL
    else:
        # utilizamos a recursão
        return funcRecursiva()

No caso do fatorial, sabemos que o fatorial de 0 é 1, certo? Esta é a nossa parte TRIVIAL.


In [ ]:
def fatorial(n):
    # Parte TRIVIAL
    if n == 0:
        return 1
    
    # Parte GERAL ou RECURSIVA
    else:
        return n * fatorial(n-1)
    
print(fatorial(3))
print(fatorial(6))
print(fatorial(20))

Apesar de parecer que a recursão complica a solução, conforme demosntrado no fatorial (exemplo anterior), uma função recursiva pode facilitar a implementação de muitos problemas, pois permite dividir o problema em problemas menores para solucioná-lo.

Para entener melhor a motivação do uso da recursão, vamos pegar um exemplo mais complexo. Lembram da torre de Hanói?

Caso não lembre, clique aqui antes de continuar lendo.

Como podemos construir um algoritmo para solucionar a torre de Hanói?

Não é trivial. Mas podemos utilizar a recursão com o objetivo de dividir para conquistar.

A chave para resolver este problema é quebrar o desafio em desafios menores até que o desafio seja trivial. Neste caso, o trivial é quando temos apenas um único disco para mover (neste caso, basta mover o disco direto).

Se queremos mover os discos da haste 1 para a haste 3, e existem $n$ discos na haste 1, podemos mover os $n-1$ discos para a haste auxiliar 2, mover o último disco da haste 1 para a haste 3 e, então, levar os $n-1$ discos da haste auxiliar 2 para a haste 3. Veja a figura a seguir:

Ou seja, a parte TRIVIAL da nossa recursão é mover um disco da haste 1 para a haste C. A parte GERAL (recursiva) vai ser mover os $n-1$ discos da haste 1 para a haste 2 e depois mover da haste 2 para a haste 3.

def hanoi():
    # Parte TRIVIAL
    if numero_de_discos == 1:
        <mover_o_disco_da_origem_para_o_destino>

    # Parte GERAL
    else:
        hanoi(<mover_n-1_discos_da_origem_para_o_auxiliar)
        <mover_o_disco_da_origem_para_o_destino>
        hanoi(<mover_n-1_discos_do_auxiliar_para_o_destino)

Veja a implmementação correta abaixo.


In [5]:
def hanoi(num_discos, hasteOrigem, hasteDestino, hasteAuxiliar):
    # Parte TRIVIAL
    if num_discos == 1:
        print ("Mover o disco da haste " + hasteOrigem + " para a haste " + hasteDestino + ".")
        
    # Parte GERAL
    else:
        hanoi(num_discos-1, hasteOrigem, hasteAuxiliar, hasteDestino)
        print ("Mover o disco da haste " + hasteOrigem + " para a haste " + hasteDestino + ".")
        hanoi(num_discos-1, hasteAuxiliar, hasteDestino, hasteOrigem)
               
hanoi(4, "A", "C", "B")


Mover o disco da haste A para a haste B.
Mover o disco da haste A para a haste C.
Mover o disco da haste B para a haste C.
Mover o disco da haste A para a haste B.
Mover o disco da haste C para a haste A.
Mover o disco da haste C para a haste B.
Mover o disco da haste A para a haste B.
Mover o disco da haste A para a haste C.
Mover o disco da haste B para a haste C.
Mover o disco da haste B para a haste A.
Mover o disco da haste C para a haste A.
Mover o disco da haste B para a haste C.
Mover o disco da haste A para a haste B.
Mover o disco da haste A para a haste C.
Mover o disco da haste B para a haste C.

Funcionou? Verifique:

Agora tente desenvolver uma outra solução para o problema da torre de Hanói sem utilizar a recursão. Pense um pouco e você perceberá que é muito difícil (porém não impossível).

Agora que entendemos a ideia da recursão, vamos analizar passo-a-passo o que acontece com o programa durante uma recursão, do ponto de vista computacional.

Vamos começar com uma função fotorial bem simples: um contador regressivo!


In [6]:
from time import sleep
def contadorRegressivo(n):
    if n == 0:
        print ("BOOM!")
    else:
        print (str(n) + "s")
        sleep(1)
        contadorRegressivo(n-1)

contadorRegressivo(3)


3s
2s
1s
BOOM!

Neste exemplo, ao chamar a função contadorRegressivo(n = 3), o Python irá verificar que n é diferetne de 0, imprimir "3s", dormir por um segundo e chamar a função contadorRegressivo(n = 2). A função contadorRegressivo(n = 2) irá seguir seguir o mesmo roteiro, mas com o valor de n igual a 2. O mesmo ocorre com a função contadorRegressivo(n = 1). Por fim, a função contadorRegressivo(n = 0) cai na parte trivial da recursão, o que faz imprimir "BOOM!" e retornar (sem recursão).

Neste momento, a função contadorRegressivo(n = 0) retorna para a função contadorRegressivo(n = 1), que retorna para a função contadorRegressivo(n = 2) que, por fim, retorna para a função contadorRegressivo(n = 3).

Note que embora o nome da função seja a mesma, os parâmetros são diferentes, ou seja, embora esteja executando o memso código, todos os valores da função são diferentes, pois a instância de cada função é diferente.

O código abaixo imprime é similar ao exemplo anterior, mas imprime passo a passo a recursão.


In [7]:
space = ""
from time import sleep
def contadorRegressivo(n):
    space = "||||" * (3-n)
    print(space + "contadorRegressivo(n = {}) # Chamado pela {}ª vêz!".format(n,4-n))
    if n == 0:
        print(space + "    if {} == 0: (VERDADEIRO)".format(n))
        print(space + "        print (\"BOOM!\")")
    else:
        print(space + "    if {} == 0: (FALSO)".format(n))
        print(space + "    else:")
        print(space + "        print (str(n = {}) + \"s\")".format(n))
        print(space + "        sleep(1)")
        print(space + "        contadorRegressivo(n-1) # Recursão!")
        contadorRegressivo(n-1)
    if n < 3:
        print(space + "    # Retornando para contadorRegressivo(n = {})".format(n+1))
    else:
        print("    # Fim da execução")

contadorRegressivo(3)


contadorRegressivo(n = 3) # Chamado pela 1ª vêz!
    if 3 == 0: (FALSO)
    else:
        print (str(n = 3) + "s")
        sleep(1)
        contadorRegressivo(n-1) # Recursão!
||||contadorRegressivo(n = 2) # Chamado pela 2ª vêz!
||||    if 2 == 0: (FALSO)
||||    else:
||||        print (str(n = 2) + "s")
||||        sleep(1)
||||        contadorRegressivo(n-1) # Recursão!
||||||||contadorRegressivo(n = 1) # Chamado pela 3ª vêz!
||||||||    if 1 == 0: (FALSO)
||||||||    else:
||||||||        print (str(n = 1) + "s")
||||||||        sleep(1)
||||||||        contadorRegressivo(n-1) # Recursão!
||||||||||||contadorRegressivo(n = 0) # Chamado pela 4ª vêz!
||||||||||||    if 0 == 0: (VERDADEIRO)
||||||||||||        print ("BOOM!")
||||||||||||    # Retornando para contadorRegressivo(n = 1)
||||||||    # Retornando para contadorRegressivo(n = 2)
||||    # Retornando para contadorRegressivo(n = 3)
    # Fim da execução

Atividade 1

A sequência de Fibonacci é uma série que segue a seguinte regra: $x_{n}=x_{n-1}+x_{n-2}$, sendo que $x_{1} = 1$ e $x_{2} = 1$. Os primeiros elementos da sequência de Fibonacci são: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, ...

Um possível código para imprimir a sequencia de Fibonacci é apresentado abaixo:


In [8]:
n1 = 1
n2 = 1
for i in range(1, 20):
    n1, n2 = n2, (n1+n2)
    print (n1)
print ("...")


1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
...

Faça um código que calcule a sequência de Fibonacci utilizando recursão.


In [9]:
def Fibonacci(n):
    """
        Código
    """

Fibonacci(5)
Fibonacci(7)

Atividade 2

Pode-se calcular o máximo divisor comum (MDC) entre dois números inteiros positivos utilizando o algoritmo de Euclides.

Veja a implementação do algoritmo de Euclides abaixo:


In [10]:
def Euclides (a, b):
    while b != 0:
        a, b = b, a % b
    return a

print(Euclides(10, 8))
print(Euclides(21, 13))
print(Euclides(63, 108))


2
1
9

Modifique o algoritmo acima de modo a utilizar recursão.