Estrutura de Dados para
Tecnologia
Roteiro
Definindo recursividade
Funções recursivas
Caso base e passo indutivo
Procedimentos recursivos
Definindo Recursividade
Um objeto é dito recursivo se ele constituir
parcialmente ou for definido em termos de si
próprio.
Existem várias operações matemáticas que
naturalmente são recursivas, como:
Multiplicação de números:
A x B = A + A + A + … + A (B vezes) = A + (A x (B–1)) Se B = 1, não é necessário realizar mais a operação.
Fatorial:
fat(5) = 5.4.3.2.1 = 5 x fat(4) = 5 x 4 x fat(3) = …
Definindo Recursividade
Portanto, há problemas que, por sua própria
definição, são recursivos !
Quando pensamos em uma solução para este
problema, seguindo a natureza recursiva,
então passamos a trabalhar com algoritmos
recursivos e, conseqüentemente, com
Algoritmos recursivos
Todo algoritmo recursivo deve possuir dois
elementos:
Uma chamada recursiva, ou seja, uma chamada
ao próprio algoritmo, considerando apenas parte do problema a ser tratado, ou o problema como
um todo. Esta chamada é conhecida como “passo de recursão” ou “passo de indução”.
Um ponto aonde não é necessário fazer a
chamada ao próprio procedimento. Isto é
Algoritmos recursivos
É importante definirmos o caso base, pois de
outro modo o algoritmo não teria um ponto final de execução, ou seja, seria executado
infinitamente. Esse é o principal cuidado que devemos ter ao trabalharmos com algoritmos recursivos.
indutivo
passo
base
caso
Algoritmos recursivos
Exemplos de algoritmos recursivos:
Algoritmos recursivos
Os exemplos vistos anteriormente são
funções recursivas, podendo ser
implementados em linguagem C ou qualquer
linguagem de 3
ageração ou superior.
Para utilizar funções recursivas, basta efetuar
uma chamada à própria função em seu corpo,
como faríamos com qualquer outra função.
Temos que observar se em algum momento
Algoritmos recursivos
Vantagens:
Redução do tamanho do código fonte
Permite descrever algoritmos de forma mais clara
e concisa
Desvantagens
Redução do desempenho de execução devido ao
tempo para gerenciamento de chamadas
Dificuldades na depuração de programas
Funções Recursivas
Implementação da função para calcular
n!
:
unsigned long int fat(int n)
{
if (n == 0) return 1;
else return (n * fat(n-1));
Funções Recursivas
Comparação entre as duas implementações:
Função não recursiva
Função recursiva
unsigned long int fat(int n);
{
int i,x;
x = 1;
for (i=n; i > 1; i--) x = x*i;
return x;
}
unsigned long int fat(int n)
{
if (n == 0) return 1;
Funções Recursivas
Chamadas recursivas:
fat(5)
5 * fat(4)
4 * fat(3)
3 * fat(2)
2 * fat(1)
1 1
2 6
Funções Recursivas
Implementação da função para a
multiplicação de dois números inteiros a e b:
int multiplica(int a,int b) {
if (b < 0)
return -multiplica(a,-b); else
if (b == 0) return 0; else
Funções Recursivas
Implementação da função para a potenciação
de dois números inteiros a e b:
float potencia(int a,int b) {
if (b < 0)
return (1 / potencia(a,-b)); else
if (b == 0) return 1; else
Funções Recursivas
Empilhamento:
De forma diferente de uma função não recursiva, cada
chamada de uma função recursiva provoca empilhamento de seus dados em memória.
Todos os dados não globais são armazenados na pilha,
informando o resultado corrente. Quando uma ativação anterior prossegue, os dados da pilha são recuperados
Isto quer dizer que quanto mais chamadas fazemos a uma
função recursiva, mais recursos de memória são necessários para executar o programa, o que pode torná-lo lento ou
computacionalmente inviável !
É importante avaliar, então, se é melhor utilizar funções
Funções Recursivas
Exercícios:
Escreva uma função recursiva que calcule o MDC (máximo
divisor comum) de dois números inteiros X e Y.
Para o cálculo, considere:
Escreva uma função recursiva que retorne o i-ésimo termo
da série de Fibonacci
0 ), mod , ( ), , ( 0 , ) , ( Y se Y X Y MDC Y X se X Y MDC Y se X Y X MDC
1, 1 2
)
Procedimentos Recursivos
Procedimentos recursivos introduzem a possibilidade
de iterações que podem não terminar: existe a necessidade de considerar o problema de
terminação.
É fundamental que a chamada recursiva a um
procedimento P esteja sujeita a uma condição A, a
qual se torna satisfeita em algum momento da computação.
Condição de terminação:
Permite que o procedimento deixe de ser executado
O procedimento deve ter pelo menos um caso básico para
Procedimentos Recursivos
Exemplo: inclusão de um novo nó ao final de uma
lista linear simplesmente encadeada
l
Procedimento não recursivo:
void insere_fim(lista *l, int valor) {
lista aux;
node* no = (node *) malloc(sizeof(node)); no->valor = valor;
no->proximo = NULL;
if(*l == NULL) // A lista está vazia ? *l = no;
else {
aux = *l;
while(aux->proximo != NULL) // caminha até o final da lista aux = aux->proximo;
aux->proximo = no; }
Procedimento recursivo:
void insere_fim_recursivo(lista *l, int valor) {
node* no;
if(*l == NULL) // Ponto onde o elemento será inserido {
no = (node *) malloc(sizeof(node)); no->valor = valor;
no->proximo = NULL; *l = no;
} else
insere_fim_recursivo(&(*l)->proximo, valor);
Procedimentos Recursivos
Caminhamento sobre lista encadeada:
Procedimento não recursivo Procedimento recursivo
void mostra_lista(lista l) {
if(l == NULL) // A lista está vazia ? printf("A lista está vazia !\n"); else
{
printf("Elementos da lista: "); while(l != NULL)
{
printf("%5d --> ",l->valor); l = l->proximo;
} }
void mostra_lista_recursivo(lista l)
{
if(l == NULL) // A lista está vazia ?
printf("/");
else
{
printf("%5d --> ",l->valor);
mostra_lista_recursivo(l->proximo);
}
Procedimentos Recursivos
Exemplo: Torre de Hanói
O problema da Torre de Hanói consiste de três pinos: A, B e
C, denominados respectivamente: origem, trabalho e destino, além de n discos de diâmetros diferentes.
Inicialmente, todos os discos encontram-se empilhados no
pino origem (A), em ordem decrescente de tamanho, de baixo para cima.
O objetivo é empilhar todos os discos no pino destino (C),
atendendo às seguintes restrições:
Apenas um disco pode ser removido de cada vez
Torre de Hanói
Solução do problema:
Naturalmente, para n > 1, o pino trabalho deverá
ser utilizado como área de armazenamento temporário.
O raciocínio para resolver o problema é
semelhante a uma prova matemática por indução. Suponha que se saiba como resolver o problema
Torre de Hanói
Solução do problema:
A extensão para n discos pode ser obtida pela realização dos
seguintes passos:
Resolver o problema da Torre de Hanói para os n-1 discos do topo do pino origem A, supondo que o pino destino seja B e o trabalho seja C;
Mover o n-ésimo pino (maior de todos) de A para C;
Resolver o problema da Torre de Hanói para os n-1 discos
localizados no pino B, suposto origem, considerando os pinos A e C como trabalho e destino, respectivamente.
Ao final destes passos, todos os discos se encontram
empilhados no disco C e as duas restrições foram satisfeitas.
Torre de Hanói
Procedimento Recursivo
void MoverDisco(int *origem, int *destino)
{
(*origem)--;
(*destino)++;
}
void Hanoi(int discos,int *A,int *C,int *B)
{
if (discos > 0) {
Hanoi(discos-1,A,B,C);
MoverDisco(A,C);
Algoritmos Recursivos
Para criar algoritmos recursivos:
Defina pelo menos um caso básico (condição de
terminação);
Quebre o problema em problemas menores,
definindo o(s) caso(s) com recursão(ões)
Faça o teste de finitude, isto é, certifique-se de