Algoritmos e Estruturas de Dados I Prof. Daniel M. Martin (daniel.martin@ufabc.edu.br) Aula 10 (Baseada nos slides do Prof. André Balan)
Recursão
Recursão Da wikipedia: A recursão é o processo pelo qual passa um certo procedimento quando um dos passos do procedimento em questão envolve a repetição completa deste mesmo procedimento. Um procedimento que se utiliza da recursão é chamado recursivo. Também é dito recursivo qualquer objeto que seja resultado de um procedimento recursivo.
Uma imagem recursiva Foto: courtesia do Prof. Jim Bryan, Univ. of BC, Canada
Relações de recorrência Em Matemática, é comum definirmos sequências de números ou funções usando recursão Exemplo 1: função fatorial
Relações de recorrência Exemplo 2: seqüência de Fibonacci F 0 = 0, F 1 = 1, F 2 = 1, F 3 = 2, F 4 = 3, F 5 = 5...
Funções recursivas em C Uma função que, em sua própria definição, chama a si mesma Exemplo: int fatorial(int n) { if (n == 1) return 1; return (n * fatorial(n 1)); }
Recursão e a pilha de execução (stack) Supõe que façamos: int x = fatorial(4);
Recursão e a pilha de execução (stack) int x = fatorial(4); x
Recursão e a pilha de execução (stack) fatorial(4) n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) fatorial(2) n 2 retorno n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) fatorial(2) fatorial(1) n 1 retorno n 2 retorno n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) fatorial(2) fatorial(1) fatorial(0) n 0 retorno n 1 retorno n 2 retorno n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) fatorial(2) fatorial(1) retorno 1 n 1 retorno n 2 retorno n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) fatorial(2) retorno 1 n 2 retorno n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) fatorial(3) retorno 2 n 3 retorno n 4 retorno x
Recursão e a pilha de execução (stack) fatorial(4) retorno 6 n 4 retorno x
Recursão e a pilha de execução (stack) retorno 24 x
Recursão e a pilha de execução (stack) int x = fatorial(4); x 24
Elementos de uma função recursiva Condição de parada ou caso base ou caso trivial: é a parte da definição da função que não faz chamada recursiva Chamada recursiva propriamente dita ou passo de recursão; deve resolver uma instância menor do mesmo problema Processamento de apoio ou processamento complementar: demais processamentos que acompanham e/ou utilizam o que resulta da chamada recursiva
Elementos de uma função recursiva Exemplo: int fatorial(int n) { if (n == 0) return 1; return (n * fatorial(n 1)); }
Elementos de uma função recursiva Exemplo: condição de parada int fatorial(int n) { if (n == 1) return 1; return (n * fatorial(n 1)); }
Elementos de uma função recursiva Exemplo: chamada recursiva a uma instância menor int fatorial(int n) { if (n == 1) return 1; return (n * fatorial(n 1)); }
Elementos de uma função recursiva Exemplo: processamento de apoio int fatorial(int n) { if (n == 1) return 1; return (n * fatorial(n 1)); }
Importante: Se não existir o caso base (condição de parada), o programa entra em loop infinito
Importante: Se não existir o caso base (condição de parada), o programa entra em loop infinito Se a chamada recursiva não for aplicada a uma instância menor do problema, o progrma entra em um loop infinito Se um função recursiva ficar chamando a si mesma indefinidamente (num loop infinito) o programa rapidamente para por estouro da pilha (stack overflow)
Mais funções recursivas em C Exemplo: números de Fibonacci int F(int n) { if (n == 0 n == 1) return n; return F(n 1) + F(n 2); }
Mais funções recursivas em C Exemplo: calculando 2^k int pot2(int k) { if (n == 0) return 1; return 2 * pot2(k - 1); }
Recursão com vários parâmetros
Recursão com vários parâmetros Exemplo: deseja-se ir da esquina A para a esquina B no mapa abaixo. As ruas são todas de mão única como indicado pelas flechas. De quantas maneiras é possível fazer isso?
Recursão com vários parâmetros De quantas maneiras é possível fazer isso?
Recursão com vários parâmetros De quantas maneiras é possível fazer isso? Do ponto A pode-se ir para norte ou para leste
Recursão com vários parâmetros O número de rotas que escolhe ir para o norte é o número de rotas entre C e B
Recursão com vários parâmetros O número de rotas que escolhe ir para o leste é o número de rotas entre D e B
Recursão com vários parâmetros Digamos que o mapa tenha m x n quarteirões (no exemplo m = 5 e n = 7)
Recursão com vários parâmetros Digamos que o mapa tenha m x n quarteirões (no exemplo m = 5 e n = 7) Faremos uma função int conta_rotas(int m, int n);
Recursão com vários parâmetros int conta_rotas(int m, int n) { }
Recursão com vários parâmetros int conta_rotas(int m, int n) { if (m == 0 n == 0) return 1; }
Recursão com vários parâmetros int conta_rotas(int m, int n) { if (m == 0 n == 0) return 1; return conta_rotas(m 1, n) + conta_rotas(m, n 1); }
Recursão com vários parâmetros int conta_rotas(int m, int n) { if (m == 0 n == 0) return 1; return conta_rotas(m 1, n) + conta_rotas(m, n 1); } Simular chamada conta_rotas(3,3); na lousa.
Versão iterativa de funções recursivas Geralmente a versão iterativa é mais rápida (pois não involve o empilhamento dos argumentos da função, do endereço de retorno, e nem o desempilhamento desses dados após a função retornar) Exemplo: fatorial int fatorial(int n) { int r; for (r = 1; n > 0; r *= n --); return r; }
Versão iterativa de funções recursivas Algumas vezes a versão iterativa é muito mais rápida pois a solução recursiva mais natural faz cálculos replicados Exemplo: simular F(5) na lousa int F(int n) { if (n == 0 n == 1) return n; return F(n 1) + F(n 2); }
Versão iterativa de funções recursivas Eis a versão iterativa da função Fibonacci int F(int n) { int i, penultimo = 0, ultimo = 1, tmp; if (n == 0 n == 1) return n; for (i = 2; i <= n; i ++) { tmp = ultimo + penultimo; penultimo = ultimo; ultimo = tmp; } return ultimo; }
Problemas tipicamente recursivos Exemplo: Torre de Hanoi
Torre de Hanoi Estado inicial: pilha de discos ordenados pelo raio Objetivo: transferir a pilha de discos para uma das outras pilhas vazias Restrição: Mover um disco por vez Um disco de raio maior não pode estar sobre um disco de raio menor
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi Ok, mas só podemos mover um disco por vez Como a pilha com os 2 discos menores foi parar na pilha auxiliar, e depois na pilha destino?
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi 1 - origem 2 - auxiliar 3 - destino
Torre de Hanoi Vamos fazer um algoritmo recursivo hanoi(n, origem, destino, auxiliar) que escreve os movimentos a serem executados passo a passo (disco a disco)
Torre de Hanoi Q: Qual o caso base?
Torre de Hanoi Q: Qual o caso base? R: quando n = 1. hanoi(1, origem, destino, auxiliar) Nesse caso basta mover o (único) disco da pilha origem para a pilha destino
Torre de Hanoi hanoi(n, origem, destino, auxiliar) se n = 1 então print move disco de + origem + para + destino senão hanoi(n 1, origem, auxiliar, destino) print move disco de + origem + para + destino hanoi(n 1, auxiliar, destino, origem)
Torre de Hanoi hanoi(n, origem, destino, auxiliar) se n = 1 então print move disco de + origem + para + destino senão hanoi(n 1, origem, auxiliar, destino) print move disco de + origem + para + destino hanoi(n 1, auxiliar, destino, origem) Exercício: implementar a função hanoi em C
hanoi(3, 1, 3, 2); Torre de Hanoi 1 2 3 Saída:
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); Torre de Hanoi 1 2 3 Saída:
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); Torre de Hanoi 1 2 3 Saída:
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi 1 2 3 Saída: move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi 1 2 3 Saída: move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 imprime move de 1 para 2 Torre de Hanoi 1 2 3 Saída: move de 1 para 3 move de 1 para 2
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 imprime move de 1 para 2 hanoi(1, 3, 2, 1); Torre de Hanoi 1 2 3 Saída: move de 1 para 3 move de 1 para 2
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 imprime move de 1 para 2 hanoi(1, 3, 2, 1); imprime move de 3 para 2 Torre de Hanoi 1 2 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 imprime move de 1 para 2 hanoi(1, 3, 2, 1); imprime move de 3 para 2 Torre de Hanoi 1 2 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 imprime move de 1 para 2 hanoi(1, 3, 2, 1); imprime move de 3 para 2 Torre de Hanoi 1 2 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 imprime move de 1 para 2 hanoi(1, 3, 2, 1); imprime move de 3 para 2 Torre de Hanoi 1 2 3 imprime move de 1 para 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 imprime move de 2 para 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1 move de 2 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 imprime move de 2 para 3 hanoi(1, 1, 3, 2); Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1 move de 2 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 imprime move de 2 para 3 hanoi(1, 1, 3, 2); imprime move de 1 para 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1 move de 2 para 3 move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 imprime move de 2 para 3 hanoi(1, 1, 3, 2); imprime move de 1 para 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1 move de 2 para 3 move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 imprime move de 2 para 3 hanoi(1, 1, 3, 2); imprime move de 1 para 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1 move de 2 para 3 move de 1 para 3
hanoi(3, 1, 3, 2); hanoi(2, 1, 2, 3); hanoi(1, 1, 3, 2); imprime move de 1 para 3 Torre de Hanoi imprime move de 1 para 2 1 2 3 hanoi(1, 3, 2, 1); imprime move de 3 para 2 imprime move de 1 para 3 hanoi(2, 2, 3, 1); hanoi(1, 2, 1, 3); imprime move de 2 para 1 imprime move de 2 para 3 hanoi(1, 1, 3, 2); imprime move de 1 para 3 Saída: move de 1 para 3 move de 1 para 2 move de 3 para 2 move de 1 para 3 move de 2 para 1 move de 2 para 3 move de 1 para 3
Torre de Hanoi http://www.youtube.com/watch?v=8z0xrw6lbc4
Recursão Caso as chamadas recursivas sejam muito numerosas (o algoritmo entre em níveis de recursão muito altos) a pilha pode estourar (stack overflow) Nesse caso deve-se construir um algoritmo iterativo (usando a estrutura de dado pilha) para simular a recursão Toda recursão pode ser transformada num algoritmo iterativo por meio de pilhas Se a chamada recursiva vem no final (tail recursion) não é necessário o uso da pilha
Backtracking
Backtracking Algoritmos recursivos também são utilizados para resolver problemas utilizando a técnica de backtracking. Consiste em escolher um determinado caminho de computação ; se este caminho o levar a um beco sem saída, volte ao ponto imediatamente antes de escolher este caminho e a partir dali tente outro.
Backtracking Exemplo: Problema das 8 rainhas
Problema das 8 rainhas Vamos fazer backtracking para resolver o problema das 8 rainhas Para ser factível em aula, vamos considerar um problema simplificado...
Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas Falhou! Backtrack
Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas Falhou! Backtrack
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas Falhou! Backtrack
Problema das 4 rainhas
Problema das 4 rainhas Falhou! Backtrack
Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Ok! Próxima linha Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Problema das 4 rainhas
Algoritmo c/ Backtracking N = total de linhas (e colunas e rainhas) total no tabuleiro n = número de linhas (e rainhas) que faltam colocar RAINHAS(int n, int N) // N é sempre igual, pode ser global... se n == 0 então // achou solução: imprime return 1; // uma soluçao foi encontrada else para j de 1 até N faça // coloca rainha na linha n coluna j se nenhuma condição é violada então r = queens(n 1, N) se r == 1 então return 1; // tira rainha da linha n coluna j return 0; // backtrack
Exercício de Laboratório Criar um programa em C para resolver o problema das 8 rainhas
Video Recursivo http://www.youtube.com/watch?v=fq2ypgoxulw