Recursão e a Pilha (Stack)

Se você não é novato em programação, e até aqueles que já fizeram algumas cadeiras/módulos de programação, provavelmente já ouviu os termos recursão e pilha.

Recursão é um método de programação onde o código executa uma função que chama a si mesma um número X de vezes para resolver um problema que apresenta um padrão repetitivo. Ela tem uma funcionalidade muito similar aos loops, mas sem explicitamente mostrar que está fazendo o mesmo trabalho repetidas vezes.

Quais as vantagens de usar recursão?

Essa é uma duvida bem comum, devo iterar? ou usar a recursão? Nos seguintes casos, seria melhor usar a recursão:

  • Reduzir o código e deixar torna-lo mais simples de ler: Para quem não está acostumado isso parece estranho, mas em grande maioria dos casos é possível escrever a mesma lógica com um número menor de linhas, tornando-o mais fácil de entender e debugar.
  • Quando você está trabalhando com uma linguagem funcional: Linguagens funcionais lidam com recursão muito melhor que imperativas. Já as linguagens imperativas lidam com iteração melhor, então leve isso em consideração quando estiver desenvolvendo seu programa ou script
  • A recursão é melhor na travessia de árvores. Este é um pouco mais avançado. Uma versão extremamente simplificada do que isso significa é a seguinte: Uma árvore é uma coleção de objetos que estão ligados um ao outro (imagine folhas em uma árvore conectada por ramos que por sua vez estão conectados a outros ramos até as raízes). Uma das maneiras mais eficientes de percorrer essas árvores ao procurar por uma folha (ou nó) específica é seguir, de maneira recursiva, uma única ramificação até o final dessa ramificação até encontrar o valor que você está procurando.

Exemplos de Recursão

Digamos que queremos criar algo simples, como uma função que faz que calcula a potencia N de um valor X (exemplo 2^3 = 8, X^N = 8).

let valor = potencia(2, 2);
//Exibe o valor 4
console.log(valor);

valor = potencia (2, 3);
//Exibe o valor 8
console.log(valor);

valor = potencia (2, 4);
//Exibe o valor 16
console.log(valor);

Importante: Este é apenas um exemplo, utilizem o objeto Math do JavaScript para esse tipo de calculo.

Poderíamos faze-lo com o Loop For

function potencia(x, n) {
  let result = 1;

  // Multiplica o resultado X N vezes para obter a resposta
  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}

Console.log(potencia(2, 3) ); // 8

Agora utilizando a recursão

function potencia(x, n) {
  if (n == 1) {
    // Quando n finalmente é 1, não executa mais a recursão para finalizar o calculo
    return x;
  } else {
    //obtém a resposta do próximo pedaço do calculo chamando a função novamente
    let recursao = potencia(x, n - 1)

    return x * recursao;
  }
}

console.log(potencia(2, 3) ); // 8

Então, o que está acontecendo exatamente?

No caso da recursão deste algoritmo, quebramos a lógica em pequenos passos que fazem a multiplicação de X pelo resultado anterior até chegar em um ponto onde não é feita mais a chamada recursiva. Quando isso ocorre, as funções começam a ser retornadas e resolvidas.

No caso do exemplo acima, se fizéssemos 2^3, a ultima recursão ser no n==1 que retornaria 2 para a função anterior. Ela multiplicaria 2 * 2, resultando em quatro e retornaria para a primeira chamada que resultaria em 8 e terminaria a função.

A recursão normalmente possui menos linhas de código

A recursão normalmente é menor que a iteração. Abaixo está o mesmo exemplo que fizemos acima, mas usando o mínimo de comandos, retornos e de linhas possíveis.

function pow(x, n) {
  return (n == 1) ? x : (x * pow(x, n - 1));
}

Pilha

Uma pilha é uma região na memória, que opera no modo First-In-Last-Out, exatamente como a pilha que você deve ter estudado em estrutura de dados. Para cada nova ação que precisa ser executada, esta é “empilhada” e terá que ser processada antes da ação anterior voltar a ser trabalhada.

Note que as pilhas não tem tamanho infinito e quando você cria uma função recursiva muito grande ou mau feita, causará o famoso erro que chamamos de stack-overflow

Para ter uma ideia, execute o seguinte código

let i=0;
function estourarPilha() {
    console.log(i++);
    estourarPilha();
}
estourarPilha();

Para cada navegador os valores serão diferentes, mas eventualmente eles irão parar de funcionar pois você conseguiu quebra-los. Mas fique tranquilo, basta reinicia-lo que tudo voltará ao normal.

Conclusão

Esta foi uma introdução bem básica sobre recursão e a pilha no JavaScript, espero que você tenha aprendido com o exemplo e caso tenha alguma duvida ou sugestão por favor deixe seu comentário abaixo.

Criando a classe pilha com JavaScript

Neste post estarei fazendo uma implementação simples da estrutura de dados conhecida como Pilha utilizando o JavaScript. Apesar da lista JavaScript ser totalmente capaz de simular a pilha, fila, lista, etc… a ideia é focar na teoria e criar do zero uma pilha de nós (nodes) como se a funcionalidade não estivesse disponível.

Como funciona a pilha?

A pilha é um tipo de estrutura de dados que funciona no formato que o último item que foi inserido será o primeiro a ser removido da estrutura (Lifo – Last in, First out). Então quando você adicionar 3 números e quiser remove-los, eles serão removidos na ordem inversa que foram adicionados (terceiro – segundo – primeiro).

Considerações

Algumas considerações na nossa implementação para que não ocorra confusão:

  • A classe do JavaScript ainda não permite declarar propriedades como privadas, por isso usarei _ na frente do nome das propriedades ao qual NÂO deveríamos estar acessando externamente.
  •  Esta é uma implementação para uso acadêmico. Por favor utilize a lista do JavaScript para trabalhar com a pilha em produção (new Array ou []).
  • Alguns trechos de código poderiam ser resumidos em uma única linha, mas foram mantidos quebrados para auxiliar aqueles que estão aprendendo.

Implementação:

Classe e Construtor

Beleza, então vamos começar. A primeira coisa que precisamos é criar a classe e o construtor dela conforme o código abaixo. Para o nome da classe usaremos o termo em português, mas para o resto iremos utilizar os termos em Inglês pois parece ser o padrão mais utilizado por livros e cursos.

class Pilha {
  constructor() {
    this._top = null;
    this._count = 0;
  }

  //Continuação do código

Como você pode ver, teremos apenas duas variáveis de controle, _top que fará referencia para o topo da pilha e _count que controlará quantos itens a pilha possuí.

 

GetCount()

O método GetCount é o mais simples de todos. Ele apenas irá retornar a quantidade de itens na pilha.

GetCount = function () {
  return this._count;
};

 

Peek()

Exibe o item no topo da pilha ou null caso ela esteja vazia.

Peek = function () {
  if (this._top) {
    return this._top.data;
  }
  return null;
};

 

Push(item)

O método Push (empurrar) é a forma como adicionaremos itens a pilha. Pelo fato de ser uma linguagem não tipada, poderemos adicionar qualquer valor a pilha que ela o aceitará.

Push = function (data) {
  let node = {
    data: data,
    next: null
  };
  node.next = this._top;
  this._top = node;
  this._count++;
};

Como você pode ver, o método cria um objeto chamado node (nó) que é um objeto JS e terá 2 propriedades, o data (dados em inglês) para o valor e next (próximo) para referência o item anterior. Após isso definimos este novo valor como sendo o do topo e referenciamos o antigo topo no next.

Isto é feito desta forma pois para a pilha, é importante termos um controle de quem está no topo. Os outros itens serão manipulados apenas quando chegar a vez deles.

 

Pop()

O Método Pop (tirar) funciona da forma oposta ao Push, ele remove o item que está no topo da fila.

Pop = function () {
  if (this._top) {
    let out = this._top;
    this._top = this._top.next;
    if (this._count > 0) {
      this._count--;
    }
    return out.data;
  }
  return null;
};

Como você pode ver o código acima faz o seguinte: Confere se existe algum item no topo da fila para poder remove-lo, transforma o next em topo, reduz a contagem em 1 e retorna o item removido ou null caso esteja vazio.

 

DisplayAll()

Este método exibe todos os itens da pilha, do topo ao fundo, no formato de um vetor JavaScript. Caso a pilha esteja vazia, retorna null

DisplayAll = function () {
  if (this._top) {
    let arr = new Array();
    let current = this._top;
    for (let i = 0; i < this._count; i++) {
      arr[i] = current.data;
      current = current.next;
    }
    return arr;
  }

  return null;
};

Apesar deste código ter um pouco mais de lógica que os métodos anteriores, ele ainda é fácil de entender. Ele testa se o _top não está vazio e se verdadeiro, percorrerá a pilha copiando o valor armazenado dentro de cada nó para dentro do vetor.


Executando o código

Após criar a classe, um teste simples ajudará a verificar que todos os métodos estão fazendo o que deveriam

let pilha = new Pilha();

console.log(`Contagem: ${pilha.GetCount()}`);

pilha.Push(4);
console.log(`Peek: ${pilha.Peek()}`);
console.log(`Contagem: ${pilha.GetCount()}`);

pilha.Push(22);

console.log(`Contagem: ${pilha.GetCount()}`);

console.log(`Pop: ${pilha.Pop()}`);
console.log(`Pop: ${pilha.Pop()}`);
console.log(`Pop: ${pilha.Pop()}`);

console.log(`Contagem: ${pilha.GetCount()}`);

pilha.Push(1);
pilha.Push(-2);
pilha.Push(100);
pilha.Push(350);

console.log(`Todos: ${pilha.DisplayAll()}`);