Dando sequência aos nossos artigos de Estruturas de Dados com JavaScript, hoje implementaremos uma Árvore de Busca Binária (Binary Search Tree). Uma árvore é uma coleção de nós conectados por arestas e é uma estrutura não-linear. Uma das principais características de uma árvore binária é que os nós com menor valor são armazenados na sua esquerda, e os nos de valor mais alto são armazenados na direita.
Para começar, criaremos uma classe chamada nó, que servirá de apoio para a criação da nossa Árvore Binária:
class No{
constructor(data, esquerda = null, direita = null){
this.data = data;
this.esquerda = esquerda;
this.direita = null;
}
}
Você pode perceber que o nó que criamos possui um dado e mais duas propriedades: esquerda e direita, que possuem valor nulo.
Agora, vamos criar a nossa Árvore:
class ArvoreBuscaBinaria{
constructor(root = null){
this.root = null;
}
}
A nossa árvore possui um nó raiz, que é definido como tendo valor nulo pelo construtor.
Também teremos os métodos principais da nossa árvore binária, que são:
- Insercao(data);
- InserirNo(no, novoNo);
- Remover(data);
- RemoverNo(no, key).
E os métodos auxiliares, que são:
- EncontrarMenorNo();
- EncontrarNoRaiz();
- EmOrdem(no);
- PreOrdem(no);
- PosOrdem(no);
- Pesquisar(no, data).
Insercao(data) e InserirNo(no, novoNo):
Com o método Insercao(data), inseriremos um novo nó na nossa árvore binária com o valor especificado. Esse método cria um nó a ser inserido e chamará o método InserirNo para isso. Então precisamos: Criar um novo nó e inicializá-lo com o dado a ser utilizado e, se a raiz estiver vazia, esse novo dado será a raiz da árvore. Senão, chamaremos o método InserirNo para achar a posição correta na árvore e adicionar o nó.
Insercao(data){
let novoNo = new No(data);
if (this.root === null){
this.root = novoNo;
} else {
this.InserirNo(this.root, novoNo);
}
}
O método InserirNo(no, novoNo) verifica em qual parte da árvore o nó deve ser inserido e coloca-o no lugar correto. Se o dado a ser inserido for menor que o nó raiz, o novo dado deve ir para a esquerda. Senão, o novo dado deve ir para a direita. O mesmo vale para os outros nós que já estão posicionados, ou seja, acontece uma comparação dos dados fornecidos com os dados do nó atual, movendo-se para a esquerda ou para a direita e recorrendo até encontrar um nó correto para qual o novo nó possa ser adicionado.
InserirNo(no, novoNo){
if (novoNo.data < no.data){
if (no.esquerda === null){
no.esquerda = novoNo;
} else {
this.InserirNo(no.esquerda, novoNo);
}
} else {
if (no.direita === null){
no.direita = novoNo;
} else {
this.InserirNo(no.direita, novoNo);
}
}
}
Remover(data) e RemoverNo(no, key):
Para remover dados da nossa árvore binária, utilizamos os métodos Remover(data) e RemoverNo(no, key). O método Remover chama o método RemoverNo passando o nó raiz e dados fornecidos, atualizando a raiz da árvore com o valor que é retornado pela função. O método RemoverNo procura um nó com o dado passado e executa as etapas para excluí-lo. Podemos excluir um nó passando por três cenários diferentes:
- Apenas um nó folha, que é um nó que não possui filhos. Eles são facilmente removidos.
- Um nó com filho, que se o nó tiver um filho na parte esquerda, atualizaremos o ponteiro (referência) do nó pai para o filho esquerdo do nó a ser excluído. O mesmo acontece com o filho da parte direita.
- Um nó com dois filhos, onde encontramos o nó com valor mínimo na parte direita e substituímos esse nó pelo nó com valor mínimo e removemos da árvore.
Remover(data){
this.root = this.RemoverNo(this.root, data);
}
RemoverNo(no, key){
if (no === null){
return null;
} else if (key > no.data){
no.direita = this.RemoverNo(no.direita, key);
return no;
} else {
if (no.esquerda === null && no.direita === null){
no = null;
return no;
}
if(no.esquerda === null){
no = no.direita;
return no;
} else if (no.direita === null){
no = no.esquerda;
return no;
}
let aux = this.EncontrarMenorNo(no.direita);
no.data = aux.data;
no.direita = this.RemoverNo(no.direita, aux.data);
return no;
}
}
Agora, precisamos percorrer a nossa árvore, e isso pode ser feito de diversas formas. Começaremos com o método:
EmOrdem(no):
Esse método percorre a árvore a partir de um nó. Percorre a subárvore esquerda, visita a raiz e percorre a subárvore direita.
EmOrdem(no){
if (no !== null){
this.EmOrdem(no.esquerda);
console.log(no.data);
this.EmOrdem(no.direita);
}
}
PreOrdem(no):
Esse método percorre a árvore visitando primeiro o nó raiz e, então, percorre a subárvore esquerda. Após percorrer o lado esquerdo, o método percorre a subárvore direita.
PreOrdem(no){
if (no !== null){
console.log(no.data);
this.PreOrdem(no.esquerda);
this.PreOrdem(no.direita);
}
}
PosOrdem(no):
Começa percorrendo a subárvore esquerda, depois, percorre a subárvore direita e por último, visita o nó root.
PosOrdem(no){
if (no !== null){
this.PosOrdem(no.esquerda);
this.PosOrdem(no.direita);
console.log(no.data);
}
}
Agora, precisamos declarar métodos auxiliares.
EncontrarMenorNo(no):
Procura o nó com menor valor na árvore binária. Esse método começa a busca a partir de um nó e move-se pela subárvore esquerda até encontrar um nó cujo filho esquerdo é nulo. Uma vez encontrado, retornamos.
EncontrarMenorNo(no){
if (no.esquerda===null){
return no;
} else {
return this.EncontrarMenorNo(no.esquerda);
}
}
EncontrarNoRaiz():
Retorna o nó raiz de nossa árvore:
EncontrarNoRaiz(){
return this.root;
}
Pesquisar(no, data):
Pesquisa o nó com dados que tenham o valor em toda a árvore:
Pesquisar(no, data){
if (no === null){
return null;
}
else if (data < no.data){
return this.Pesquisar(no.esquerda, data);
} else if (data > no.data){
return this.Pesquisar(no.direita, data);
} else {
return no;
}
}
Agora, precisamos criar uma nova árvore e implementar seus métodos:
let arvoreBinaria = new ArvoreBuscaBinaria();
arvoreBinaria.Insercao(20);
arvoreBinaria.Insercao(25);
arvoreBinaria.Insercao(15);
arvoreBinaria.Insercao(10);
arvoreBinaria.Insercao(28);
arvoreBinaria.Insercao(27);
arvoreBinaria.Insercao(9);
arvoreBinaria.Insercao(7);
arvoreBinaria.Insercao(2);
arvoreBinaria.Insercao(28);
let raiz = arvoreBinaria.EncontrarNoRaiz();
arvoreBinaria.EmOrdem(raiz);
arvoreBinaria.Remover(2);
arvoreBinaria.PosOrdem(raiz);
arvoreBinaria.PreOrdem(raiz);
E nossa saída será:
Gostou deste artigo? Comente abaixo!
Referências: https://www.geeksforgeeks.org/implementation-binary-search-tree-javascript/