Em geral, problemas recursivos possuem também versões iterativas. Algumas vezes é mais fácil visualizar a solução iterativa de um problema, outras vezes a recursiva é mais simples.
Já sabemos como usar funções. Mas existe uma classe particular de funções que ainda não exploramos: as funções recursivas. Essas funções tem a particularidade de chamar elas mesmas durante sua execução, e são muito úteis para resolver problemas recursivos.
Os problemas recursivos são aqueles que podem ser definidos utilizando instâncias menores deles mesmos. Pos exemplo, a função fatorial é recursiva, podendo ser definida como:
Note na linha superior que a definição de n! utiliza a função fatorial em uma instância menor, i.e. (n-1)!. Isso caracteriza uma função fatorial. A segunda linha da definição define a condição de parada da função. Toda função recursiva possui uma condição de parada.
Em geral, problemas recursivos possuem também versões iterativas. Algumas vezes é mais fácil visualizar a solução iterativa de um problema, outras vezes a recursiva é mais simples.
Resumidamente temos:
A função de fatorial é muito simples de ser implementada, como mostrado abaixo. Note que precisamos retornar o valor calculado recursivamente para que ele chegue na chamada anterior. Se não retornarmos o valor ele será perdido. Isso é por conta da maneira que a pilha de execução funciona. Veremos isso com mais detalhes adiante.
long long factorial(int n){ if(n == 1) // caso base return 1; // caso recursivo return n * factorial(n-1); }Veja Execução Download Code
Vamos entender então como funciona a recursividade no computador. Para isso precisamos entender melhor a pilha de execução. Os conceitos básicos são os seguintes:
Vejamos um esquema.
Prev NextNote que:
Nada no computador é ilimitado, principalmente a memória. Pelo exemplo de funcionamento da pilha de execução você deve ter percebido que chamar funções ocupa memória. Como uma função recursiva faz várias chamadas de função temos que tomar alguns cuidados:
Não é tão simples decidir quando usar uma solução recursiva para um problema, mas você vai perceber que alguns problemas são muito mais fáceis e intuitivos de serem resolvidos recursivamente. É nesses casos que a recursão vale a pena.
A título de curiosidade, existe um paradigma de programação, a chamada programação funcional que faz extenso uso de recursividade nas definições de estruturas de dados e funções.
int pow_recursivo(int x, int n){ if(n == 1) return x; return x * pow_recursivo(x,n-1); }Download Code
Note que o caso base é quando temos um número X1, no qual o resultado é o próprio X, então a condição de parada é quando a potência é 1.
Quando o expoente não é 1, então basta reduzimos a potência de Xn para X¹ * Xn-1chamando recursvivamente a função.
void add(char nome[], char c){ int n = strlen(nome); nome[n] = c; nome[n+1] = '\0'; } void inverter_string(char nome[], char aux[], int n){ if(n == -1) return; add(aux,nome[n]); inverso(nome,aux,n-1); }Download Code
Existem vários métodos para se inverter uma string em c.
Uma delas é você percorrer de trás para frente uma string e ir adicionando o caracter do índice em uma string auxiliar
Note que a condição de parada é quando o índice chega em -1, pois não existe uma string com a posição -1.
int binary_search(int v[], int x, int L, int R){ if(L > R) return -1; int mid = (L+R)/2; if(v[mid] == x) return mid; else if(v[mid] > x) return binary_search(v,x,L,mid-1); else return binary_search(v,x,mid+1,R); }Download Code
Note que a condição de parada acontece quando o índice da esquerda, ultrapassou o indice da direita, ou seja, ambos se cruzaram, logo não o número buscado não foi encontrado.
Quando o elemento da posição do mid é igual ao que estamos buscando, então retornamos este índice
Caso o valor encontrado seja maior que o buscado, basta repetir a busca no intervalo com os valores menores que ele (lado esquerdo), reduzindo o R para mid - 1.
Caso contrário, repetir a busca no intervalo com os valores maiores que o atual (lado direito), subindo o L para mid+1.