Você está na página 1de 17

Árvore binária

O tipo abstrato árvore pode ser subdividido de acordo com algumas


características específicas, sendo uma delas as árvores binárias que são aquelas cujo
cada nó possui no máximo dois filhos, um à esquerda e outro à direita (RODRIGUES
et al., 2021, p. 115).
O Código 1-A apresenta uma representação de uma estrutura de árvore
binária.

Figura 2 | Código 1-A: Estrutura de uma árvore binária


01 #include <stdio.h>
02 #include <stdlib.h>
03 typedef struct arv{
04 int valor;
05 struct arv *esquerda;
06 struct arv *direita;
07 }arvore;
Fonte: elaborada pelo autor.

Observe no Código 1-A que foi criada uma estrutura chamada arv que foi
apelida de arvore contendo um campo do tipo inteiro (isso pode mudar de acordo com
a necessidade do desenvolvedor) e dois campos ponteiros do tipo dessa estrutura
(esquerda e direita) que terão o endereço dos nós filhos (esquerda e direita) do nó,
quando não há um filho no ramo é inserido o valor nulo no ponteiro. Para as árvores
que não são binárias esse número de ponteiros (ramos) pode ser maior.
No Código 1-B é apresentada a função que realiza a operação de criar uma
árvore binária.

Figura 3 | Código 1-B: Operação de criar uma árvore binária vazia


08 arvore* cria () {
09 return NULL;
10 }
Fonte: elaborada pelo autor.

O Código 1-B contém a função cria sem parâmetros que retorna nulo, ou seja,
cria uma árvore vazia. Já no Código 1-C apresenta a função de verificação se a árvore
está vazia.

Figura 4 | Código 1-C: Operação de verificar se a árvore está vazia


11 int vazia (arvore *a) {
12 return a==NULL;
13 }
Fonte: elaborada pelo autor.

Atente-se que o Código 1-C contém a função vazia que recebe como
parâmetro o endereço de uma estrutura do tipo arvore que irá verificar se o endereço
é nulo. Caso seja verdadeiro o retorno será um (1), pois quando uma operação
relacional na linguagem C é verdadeira a sua resposta é um (1), caso contrário, terá
como resposta o valor zero (0). No Código 1-D é apresentado uma função para criar
um nó do tipo arvore.

Figura 5 | Código 1-D: Operação de criar um novo nó


14 arvore* novo_no(int dado) {
15 arvore *a = (arvore*)malloc(sizeof(arvore));
16 a->valor = dado;
17 a->esquerda = a->direita = NULL;
18 return a;
19 }
Fonte: elaborada pelo autor.

Observe que o Código 1-D possui a função novo_no que recebe como
parâmetro o dado que será inserido no novo nó, em seguida, é alocado na memória
uma estrutura do tipo arvore e seu endereço é armazenado no ponteiro a, depois, o
campo valor recebe a informação contida no parâmetro dado e os ponteiros esquerda
e direita recebem o valor nulo, por fim, é retornado o endereço desse nó. O Código 1-
E mostra o procedimento que libera espaço de memória alocado pela função novo_no
apresentado no Código 1-D.

Figura 6 | Código 1-E: Operação de liberar o espaço alocado na criação dos nós
20 void libera(arvore *a){
21 if(!vazia(a)){
22 libera(a->esquerda);
23 libera(a->direita);
24 free(a);
25 }
26 }
Fonte: elaborada pelo autor.

Verifique que o Código 1-E tem um procedimento chamado libera que recebe
uma estrutura do tipo arvore como parâmetro, em seguida, é verificado se essa
estrutura não é vazia (aponta para nulo) através da função vazia criada no Código 1-
C, caso não seja vazia o procedimento é chamado novamente para o nó a esquerda,
provocando assim uma recursão, na linha abaixo o procedimento é chamado também
para o nó a direita, depois, é liberado o nó atual. Analise que primeiro é liberado os
nós filhos e só depois nó atual, pois se liberar o nó atual antes dos nós filhos se perder
as informações dos endereços dos filhos que estão armazenados na estrutura do nó
atual. O Código 1-F apresenta o procedimento para imprimir na tela os valores da
árvore.

Figura 7 | Código 1-F: Operação de mostrar na tela os valores da árvore


27 void imprime_pre(arvore *a){
28 if (!vazia(a)) {
29 printf("%i\n",a->valor);
30 imprime_pre(a->esquerda);
31 imprime_pre(a->direita);
32 }
33 }
Fonte: elaborada pelo autor.

Perceba que o Código 1-F tem um procedimento chamado de imprime_pre


que recebe uma estrutura do tipo arvore como parâmetro, em seguida, é verificado se
essa estrutura não é vazia (aponta para nulo), caso não seja mostra-se a informação
contida no campo valor e pula uma linha, em seguida, chama-se o procedimento
novamente (gerando uma recursividade) para o nó da esquerda, depois chama-se
para o nó a direita. Observe que primeiro mostra-se o valor do nó atual e depois
chama-se o procedimento para o ramo a esquerda e direita, essa ordem para
percorrer os nós se chama de pré-ordem. Se fizermos a inversão entre as linhas 29 e
30 o código passaria a chamar primeiro o procedimento para o ramo da esquerda, em
seguida, mostrar o valor do nó atual e depois chamar o procedimento para o ramo da
direita, essa forma de percorrer é chamada de in-ordem. Já se mover a linha 29 entre
as linhas 31 e 32 fará que seja chamado o procedimento para o ramo da esquerda,
em seguida, chamado para o ramo da direita e depois mostrado o valor do nó atual,
essa maneira de percorrer é chamada de pós-ordem. Nas três formas são mostradas
as informações de todos os nós, mas cada uma mostra os dados em uma ordem
diferente. O Código 1-G apresenta o programa principal para mostrar o funcionamento
das operações criadas.

Figura 8 | Código 1-G: Programa principal usando as operações da árvore


34 void main(){
35 arvore *arv = cria();
36 arv=novo_no(4);
37 arv->esquerda=novo_no(1);
38 arv->direita=novo_no(7);
39 imprime_pre(arv);
40 libera(arv);
41 }
Fonte: elaborada pelo autor.

Na linha 34 do Código 1-G foi criado o procedimento principal (main) que inicia
utilizando a função cria e seu retorno é gravado no ponteiro arv do tipo arvore, após a
execução desse comando (linha 35) a estrutura arv é representada na Figura 9-a,
onde o ponteiro aponta para nulo. Na linha 36 é chamada a função novo_no passando
o valor 4 como argumento e o seu retorno atualiza o endereço contido no ponteiro arv,
após essa execução a estrutura arv é retratada na Figura 9-b. Na linha 37 é chamada
novamente a função novo_no, mas agora é passado o valor 1 como argumento e o
seu retorno é gravado no ramo esquerdo do nó que contém o valor 4 (nó raiz), essa
mudança é representada pela Figura 9-c. Na linha 38 chama-se a função novo_no
novamente passando como argumento o valor 7 e o retorno é gravado no ramo direito
do nó raiz (nó com o valor 4), passando a estrutura pela mudança apresentada na
Figura 9-d.

Figura 9 | Passo a passo do programa principal


arv
arv

(a) (b)
arv arv

4 4

1 1 7
(c) (d)
Fonte: elaborada pelo autor.

Dessa forma, após a execução das linhas 35 a 38 o ponteiro arv apontará


para uma estrutura do tipo arvore que tem o campo valor com a informação 4, o seu
ponteiro da esquerda possui o endereço de uma estrutura do tipo arvore com o campo
valor contendo 1 e o seu ponteiro da direita com outra estrutura do tipo arvore com o
campo valor contendo 7, as estruturas com o valor 4 e 7 tem seus ponteiros da
esquerda e direita apontando para nulo (são folhas).
Na linha 39 é chamado o procedimento imprime_pre (que está percorrendo
em pré-ordem a árvore), com isso, é mostrado na tela o valor 4, pula-se uma linha,
em seguida, mostra-se o valor 1 e pula uma linha, por fim, mostra-se o valor 7 e pula-
se uma linha, portanto, a função mostra os valores de todos os nós da estrutura
informada para o procedimento.
Na linha 40 é chamado o procedimento libera para desalocar o espaço de
memória que foi alocado para os três nós. O procedimento inicia com o nó mais à
esquerda, com isso, é liberado o espaço alocado para o nó contendo o valor 1, em
seguida, é liberado o espaço do nó mais à direita, portanto, liberando o nó com o valor
7, por fim, é liberado o nó que não possui mais filhos alocados, dessa forma, é liberado
o nó contendo o valor 4, liberando com isso, todos nós da árvore.
Atente-se que não foi estabelecida uma regra para inserção dos números, ou
seja, se for inserir um novo nó na árvore (representada na Figura 9-d), onde deveria
ser inserido esse novo valor? Ele será filho de qual nó? Assim como, se precisar
remover um nó, como ficará sua subárvore a esquerda e a direita? Esses
questionamentos e demais não citados são respondidos no tipo abstrato de árvore
binária de busca.

Árvore binária de busca


Uma árvore binária de busca (ou ordenada) tem a seguinte propriedade: para
cada nó n da árvore, todos os valores armazenados em sua subárvore à esquerda (a
árvore cuja raiz é o filho à esquerda) são menores que o valor v armazenado em n, e
todos os valores armazenados na subárvore à direita são maiores ou igual a v.
(DROZDEK, 2016, p. 189).
Como a árvore binária de busca é uma árvore binária será reutilizado Código
1 apresentado com a estrutura e as operações da árvore binária, sendo removido
somente a parte do programa principal e realizando a alteração no procedimento de
imprimir os nós, modificando a ordem para in-ordem (já comentado no Código 1-F).

Figura 10 | Código 2-A: Estrutura e Operações da árvore binária


01 #include <stdio.h>
02 #include <stdlib.h>
03 typedef struct arv{
04 int valor;
05 struct arv *esquerda;
06 struct arv *direita;
07 }arvore;
08 arvore* cria () {
09 return NULL;
10 }
11 int vazia(arvore *a) {
12 return a==NULL;
13 }
14 arvore* novo_no(int dado) {
15 arvore *a = (arvore*)malloc(sizeof(arvore));
16 a->valor = dado;
17 a->esquerda = a->direita = NULL;
18 return a;
19 }
20 void libera(arvore *a){
21 if (!vazia(a)){
22 libera(a->esquerda);
23 libera(a->direita);
24 free(a);
25 }
26 }
27 void imprime_ordem(arvore *a){
28 if (!vazia(a)) {
29 imprime_ordem(a->esquerda);
30 printf("%i\n",a->valor);
31 imprime_ordem(a->direita);
32 }
33 }
Fonte: elaborada pelo autor.

Para seguir as regras de uma árvore binária de busca, ou seja, possuir na


subárvore à esquerda do nó todos os números menores que o valor do nó e na
subárvore à direita do nó todos os números maiores ou igual ao valor do nó será
necessário implementar uma função inserir, conforme mostra o Código 2-B.
Figura 11 | Código 2-B: Operação de inserir na árvore binária de busca
34 arvore* insere(arvore *a, int dado) {
35 if(vazia(a)) {
36 a = novo_no(dado);
37 }else if (dado < a->valor)
38 a->esquerda = insere(a->esquerda,dado);
39 else
40 a->direita = insere(a->direita,dado);
41 return a;
42 }
Fonte: elaborada pelo autor.

O Código 2-B possui uma função chamada insere que recebe como parâmetro
uma estrutura de árvore e um valor inteiro chamado dado. Na linha 35 é verificado se
a estrutura passada por parâmetro é vazia, se isso ocorrer é chamada a função
novo_no com o dado passado por parâmetro e seu retorno (endereço do novo nó) é
armazenado no ponteiro a.
Caso isso não ocorra, na linha 37 é verificado se o valor a ser inserido é menor
que o valor contido no nó, se isso ocorrer é chamada recursivamente a função insere,
passando por parâmetro a subárvore à esquerda do nó atual e o valor a ser inserido
na árvore, sendo que o retorno da função (endereço do novo nó) é armazenado no
ponteiro esquerda do nó atual.
Caso nenhuma das duas opções sejam verdadeiras, ou seja, a estrutura não
esteja vazia, ou o dado a ser inserido não seja menor que o valor do nó atual será
executada a linha 40. Dessa forma, será chamada recursivamente a função, passando
por parâmetro a subárvore da direita do nó e o valor que deseja ser inserido e o retorno
da função, será armazenado no ponteiro direita do nó atual.
Em suma, a função insere se o nó está vazio (aponta para nulo), caso
contrário chama a função para o ramo esquerdo quando o valor a ser inserido é menor
que o valor do nó e chama a função para o ramo direito quando o valor a ser inserido
é maior ou igual ao valor do nó atual.
O Código 2-C apresenta a principal vantagem desse tipo abstrato de dados,
que é a busca de uma certa informação em uma árvore binária.

Figura 12 | Código 2-C: Operação de busca nó na árvore binária de busca


43 arvore* busca(arvore *a, int dado) {
44 if (vazia(a)) return NULL;
45 else if (a->valor > dado)
46 return busca(a->esquerda, dado);
47 else if (a->valor < dado)
48 return busca(a->direita, dado);
49 else return a;
50 }
Fonte: elaborada pelo autor.

O Código 2-C apresenta similaridade com o Código 2-B, pois a lógica para
localizar onde inserir um novo valor na árvore binária de busca é igual a de localizar
um valor. Dessa forma, a função busca recebe como parâmetro uma estrutura de
árvore e um valor inteiro chamado dado (que será buscado). Na linha 44 é verificado
se a estrutura passada por parâmetro é vazia, se isso ocorrer será retornado nulo,
referenciando que o valor não foi encontrado.
Caso não seja nulo, na linha 45 é verificado se o valor do nó é maior que o
valor passado por parâmetro, se for, retorna-se o resultado de uma chamada recursiva
informando o ramo a esquerda da árvore (filho a esquerda) e o valor que será
buscado. Essa ação é realizada, pois uma árvore binária de busca possui a esquerda
do nó todos os valores menores que o nó, não havendo a necessidade de verificar no
ramo a direita.
Caso o valor do nó não seja maior, na linha 47 é verificado se o valor do nó é
menor que o valor buscado, se for, retorna-se o resultado de uma chamada recursiva
passando o ramo a direita (filho a direita) e novamente o valor buscado.
Caso o nó não seja nulo, o valor do nó não seja menor ou maior, somente
restará que ele seja igual ao valor buscado, dessa forma, será executada a linha 49,
retornando o endereço do nó atual.
O Código 2-D apresenta a operação de excluir um nó em uma árvore binária
de busca localizando o valor do nó. Essa é a operação mais complexa vista até o
momento, então fique atento a cada passo do código.

Figura 13 | Código 2-D: Operação de excluir nó na árvore binária de busca


51 arvore* exclui(arvore* a, int dado) {
52 if (vazia(a)) return NULL;
53 else if (a->valor > dado)
54 a->esquerda = exclui(a->esquerda, dado);
55 else if (a->valor < dado)
56 a->direita = exclui(a->direita, dado);
57 else {
58 if(a->esquerda==NULL && a->direita==NULL){
59 free(a);
60 a=NULL;
61 }else if (a->esquerda==NULL) {
62 arvore *temp = a;
63 a = a->direita;
64 free(temp);
65 }else if (a->direita == NULL) {
66 arvore *temp = a;
67 a = a->esquerda;
68 free (temp);
69 }else {
70 arvore *f = a->esquerda;
71 while (f->direita!=NULL) f = f->direita;
72 a->valor = f->valor;
73 f->valor = dado;
74 a->esquerda = exclui(a->esquerda, dado);
75 }
76 }
77 return a;
78 }
Fonte: elaborado pelo autor.

O Código 2-D possui uma função chamada exclui que recebe como parâmetro
uma estrutura de árvore e um valor inteiro chamado dado (que será removido). Na
linha 52 está sendo verificado se a estrutura passada por parâmetro é vazia, se isso
ocorrer será retornado nulo, referenciando que o valor não foi encontrado e por isso
não pode ser removido.
Caso a estrutura não seja nula, na linha 53 é verificado se o valor do nó é
maior que o valor informado para ser removido, se isso ocorrer, será chamada
recursivamente a função passando o ramo da esquerda e o valor para ser removido,
sendo que o endereço de retorno da função será o ramo da esquerda do nó atual.
Essa ação ocorre para atualizar o ramo, pois a raiz dessa subárvore pode ser o nó
que será excluído, havendo a necessidade da alteração de seu endereço.
Em seguida, na linha 55 é verificado se o valor do nó atual é menor que o
valor a ser removido, se isso ocorrer, será chamada a função recursivamente
passando o ramo a direita e o valor que deseja ser removido, dado que o retorno da
função será o ramo da direita do nó atual, da mesma forma que ocorreu no ramo da
esquerda. Observe que até esse trecho do código a lógica é similar aos códigos de
inserir (Código 2-B) e buscar (Código 2-C), pois nestes três casos está percorrendo a
árvore binário de busca através da recursividade. Portanto, esse trecho do Código 2-
D, entre as linhas 52 e 56, está buscando o valor que será removido.
A linha 57 será executada se a estrutura não for nula, se o valor do nó atual
não seja menor ou maior que o dado a ser removido, portanto, será executada se o
nó atual contém o mesmo valor que deseja ser removido. Mas antes de remover o nó
é necessário verificar as situações que esse nó que será removido, pois esse nó pode
ser externo (folha) ou interno (possui pelo menos um filho).
Na linha 58 é verificando se o campo esquerda e direita do nó atual são nulos,
ou seja, se o nó atual é uma folha, se isso ocorrer será liberado o espaço em memória
do nó e atribuído ao nó atual o valor nulo.
Caso o nó atual não seja uma folha, resta verificar o nó que possuem um ou
dois filhos, por isso, na linha 61 é verificando se o nó não possui filho a esquerda,
portanto, só possui um filho a direita, com isso, na linha 62 é criado um ponteiro para
a estrutura do tipo arvore que recebe o endereço do nó atual, em seguida, o nó atual
passa a ser o seu filho a direita (muda-se o endereço do ponteiro a para o endereço
que estava no seu filho a direita), concluindo na linha 64 a liberação do nó que possui
o valor que deseja ser removido.
Na linha 65 é verificado se o nó não possui filho a direita, no entanto, só possui
um filho a esquerda e, com isso, é realizada a mesma lógica entre as linhas 62 e 64,
mas agora para outro lado da árvore. Portanto, na linha 66 é criado um ponteiro para
a estrutura do tipo arvore que recebe o endereço do nó atual, em seguida, o nó atual
passa a ser o seu filho a esquerda (muda-se o endereço do ponteiro a para o endereço
que estava no seu filho a esquerda) e na linha 68 é liberado o espaço alocado para o
nó que possui o valor que deseja ser removido.
A linha 69 será executada se o nó possui dois filhos, neste caso, é necessário
que algum filho assume o lugar do pai (nó atual), há duas formas de resolver essa
situação, substituir o nó atual pela folha mais à direita do seu filho a esquerda (o maior
nó entre os valores menores que o nó) ou substituir pela folha mais à esquerda do seu
filho a direita (o menor nó entre os valores maiores que o nó). Neste código optou-se
pela segunda opção, pois na linha 70 cria-se um ponteiro da estrutura do tipo arvore
que recebe o endereço do filho à esquerda do nó atual, em seguida, na linha 71 é
criado uma estrutura de repetição que percorrerá essa subárvore (o nó atual receberá
o endereço do seu filho a direita) até que o filho a direita do nó seja diferente de nulo.
Portanto, ao final da estrutura de repetição, o ponteiro f terá o endereço do nó (interno
ou externo) com maior valor entre o ramo da esquerda do nó a ser removido, após,
na linha 72 e 73 são invertidos os valores entre os nós (o nó a ser removido e o nó
com o maior número entre os menores), sendo que na linha 72 o nó com o valor a ser
removido recebe o valor que será substituído) e na linha 73 o nó que será substituído
receberá o valor a ser removido. Ao final, na linha 74 é chamada a função
recursivamente passando o ramo a esquerda do nó atual (onde contém o nó que
deverá ser removido em nova posição) e o endereço de retorno é armazenado no
endereço do filho a esquerda. Por fim, é retornado na linha 77 o endereço do nó para
atualizar a árvore com o nó removido.
No Código 2-E apresenta um exemplo das operações em uma árvore binária
de busca.

Figura 14 | Código 2-E: Programa principal executando as operações da árvore binária de


busca
79 void main(){
80 arvore *arv = cria();
81 arv=insere(arv,6);
82 arv=insere(arv,3);
83 arv=insere(arv,4);
84 arv=insere(arv,9);
85 arv=insere(arv,1);
86 arv=insere(arv,7);
87 arv=insere(arv,10);
88 arv=insere(arv,5);
89 printf("Arvore com 8 nos:\n");
90 imprime_ordem(arv);
91 printf("Encontrou o valor %i\n",busca(arv,1)->valor);
92 arv=exclui(arv,10);
93 arv=exclui(arv,6);
94 arv=exclui(arv,9);
95 printf("Arvore com 5 nos:\n");
96 imprime_ordem(arv);
97 libera(arv);
98 }
Fonte: elaborada pelo autor.

O Código 2-E inicia o programa principal criando, na linha 80, um ponteiro do


tipo arvore que recebe o retorno da função cria, que retorna o endereço nulo. Em
seguida, entre as linhas 81 e 88 são inseridos os valores 6, 3, 4, 9, 1, 7, 10, 5,
respectivamente. Destaca-se que a ordem de inserir os valores na árvore binária de
busca influencia em como ela será montada. Na Figura 15 está mostrando o passo a
passo de cada inserção na árvore.
Figura 15 | Operações de inserir na árvore binária de busca

6 6
3
(a) (b)

6 6
3 3 9

4 4

(c) (d)

6 6
3 9 3 9

1 4 1 4 7

(e) (f)

6 6
3 9 3 9

1 4 7 10 1 4 7 10

5
(g) (h)
Fonte: elaborada pelo autor.

Na linha 81 é chamada a função para inserir o valor 6 na árvore recém-criada


(vazia) na linha 80. Recordando de uma forma breve a lógica da função insere
apresentada no Código 2-B, se o nó é vazio faz a inserção, caso o valor é menor que
o nó é chamado a função recursivamente para a subárvore a esquerda, senão chama
a função para a subárvore à direita, por isso, será criado um nó, inserido o valor 6
neste nó e ele será o único nó da árvore, conforme mostrado na Figura 15-a.
Em seguida, na linha 82 é chamada a função insere com o valor 3, como o
valor 3 é menor que o valor do nó atual (valor 6), a função é chamada para a subárvore
a esquerda, nesta nova chamada verifica que o nó é vazio, assim, criando um nó,
inserido o valor 3 e o endereço do nó é armazenado no nó atual (subárvore a esquerda
do nó 6), conforme mostra a Figura 15-b.
Na linha 83 é chamada a função insere com o valor 4, como o valor 4 é menor
que o valor do nó atual (valor 6), a função é chamada para a subárvore a esquerda,
agora o nó atual contém o valor 3 e como o nó atual é menor que o valor 4, haverá
uma nova chamada da função insere para a subárvore à direita, como o nó atual passa
a ser vazio, será criando um novo nó, inserido o valor 4 e o endereço do nó é
armazenado no nó atual (subárvore a direita do nó 3), conforme mostra a Figura 15-
c.
Já na linha 84, é solicitado a inserção do valor 9, como o valor 9 é maior que
o valor do nó atual (valor 6), a função é chamada para a subárvore a direita, nesta
nova chamada o nó atual passa a ser vazio, com isso, será criando um novo nó,
inserido o valor 9 e o endereço do nó é armazenado no nó atual (subárvore a direita
do nó 6), conforme mostra a Figura 15-d.
Na linha seguinte, a linha 85, é chamada a função insere com o valor 1, como
o valor 1 é menor que o valor do nó atual (valor 6), a função é chamada para a
subárvore a esquerda, agora o nó atual contém o valor 3 e como o nó atual é maior
que o valor 1, haverá uma nova chamada da função insere para a subárvore à
esquerda, como o nó atual passa a ser vazio, será criando um novo nó, inserido o
valor 1 e o endereço do nó é armazenado no nó atual (subárvore a esquerda do nó
3), conforme mostra a Figura 15-e.
Na sequência, a linha 86 é inserido o valor 7, como o valor 7 é maior que o
valor do nó atual (valor 6), a função é chamada para a subárvore a direita, nesta nova
chamada o nó atual contém o valor 9 e como o nó atual é maior que o valor 7, haverá
uma nova chamada da função insere para a subárvore à esquerda, com isso, será
criando um novo nó, inserido o valor 7 e o endereço do nó é armazenado no nó atual
(subárvore a esquerda do nó 9), conforme mostra a Figura 15-f.
Na linha 87 é solicitado a inserção do valor 10, como o valor 10 é maior que o
valor do nó atual (valor 6), a função é chamada para a subárvore a direita, nesta nova
chamada o nó atual contém o valor 9 e como o nó atual é menor que o valor 10, haverá
uma nova chamada da função insere para a subárvore à direita, com isso, será criando
um novo nó, inserido o valor 10 e o endereço do nó é armazenado no nó atual
(subárvore a direita do nó 9), conforme mostra a Figura 15-g.
A última inserção ocorre na linha 88, solicitando a inserção do valor 5, como
o valor 5 é menor que o valor do nó atual (valor 6), a função é chamada para a
subárvore a esquerda, nesta nova chamada o nó atual contém o valor 3 e como o nó
atual é menor que o valor 5, haverá uma nova chamada da função insere para a
subárvore à direita, com isso, novamente o nó atual (valor 4) é menor que o valor 5,
haverá uma nova chamada da função insere para a subárvore à direita, com isso, será
criando um novo nó, inserido o valor 5 e o endereço do nó é armazenado no nó atual
(subárvore a direita do nó 4), conforme mostra a Figura 15-h.
A linha 89 mostra o texto ”Arvore com 8 nos:” e pula uma linha, para na
sequência, na linha 90 seja mostrado os valores em ordem, ou seja, será mostrado
na tela os valores 1, 3, 4, 5, 6, 7, 9 e 10 em cada linha. Recordando que no Código 1-
F que apresentava o procedimento imprime_pre estava em pré-ordem, ou seja,
mostrava-se o valor do nó atual e depois chamava a função recursivamente para as
subárvores a esquerda e direita, respectivamente. Como no Código 2-A foi alterado a
forma de percorrer os nós para em ordem, dessa forma, será chamada a função
recursivamente para a subárvore a esquerda, após mostra-se o valor do nó atual, por
fim, chama a função recursivamente para a subárvore a direita.
Na sequência, mas especificamente na linha 91, é chamada a função busca
apresentada no Código 2-C e seu retorno é mostrado com a concatenação do texto
“Encontrou o valor “. Neste caso foi solicitada a busca pelo nó com o valor 1, para
localizar esse valor a função inicia com a verificação do nó raiz com vazio, como não
é, é verificado se o nó atual (raiz) é maior que o valor 1, como é, chama-se a função
recursivamente para a subárvore a esquerda. Na segunda chamada da função agora
o nó raiz possui o valor 3 e é verificado novamente as mesmas informações, ou seja,
se o nó é vazio, como não é, verifica-se se o nó atual é maior que o valor 1, como é
verdadeiro, chama-se novamente a função (terceira vez) para a subárvore a esquerda.
Por fim, nesta terceira chamada da função, o nó raiz possui o valor 1 (qual foi solicitado
encontrar), mas seguindo a função é verificado se o nó é vazio, se é maior ou menor,
como nenhuma das três verificações é verdadeira, verifica-se que o valor do nó atual
é o solicitado na busca, por isso, retorna-se o endereço do nó.
A linha 92 é solicitado a exclusão do valor 10 e segue-se os passos do Código
2-D apresentando anteriormente. Portanto, no nó raiz (atualmente como o valor 6),
conforme mostra a Figura 16-a, é verificado se o nó é vazio, como não é, verifica se o
valor do nó é maior que o valor a ser excluído, como não é, verifica se é menor, como
é verdadeiro, é chamada a função recursivamente para a subárvore a direita. Na
segunda chamada da função o nó atual possui o valor 9, sendo verificado novamente
que não é vazio, verifica se o valor do nó é maior que o valor a ser excluído, como não
é, verifica se é menor, como é verdadeiro, é chamada a função recursivamente para
a subárvore a direita. Na terceira chamada da função o nó atual possui o valor 10
(valor que deseja ser excluído), portanto, verifica se é vazio, maior ou menor, como
todos são falsas, passa-se para a próxima verificação, onde é verificado se as
subárvores a esquerda e direita são nulas (vazias), como é, realiza-se a liberação do
espaço reservado do nó por meio da função free e atribui ao nó o valor nulo e no final
do código retorna-se o nó atual, ficando a árvore conforme mostra a Figura 16-b.

Figura 16 | Operações de exclusão na árvore binária de busca

6 6
3 9 3 9

1 4 7 10 1 4 7

5 5
(a) (b)

5 5
3 9 3 9

1 4 7 1 4 7

6
(c) (d)

5 5
3 9 3 7

1 4 7 1 4

(e) (f)
Fonte: elaborada pelo autor.

A linha 93 é solicitado a exclusão do valor 6, como o nó não é vazio, menor


ou maior, ele é o nó que deve ser excluído, em seguida, é verificado se o nó é uma
folha (se as subárvores da esquerda e direita são nulos), como neste caso não é, é
verificado se ele tem um único filho a direita (não filho a esquerda), depois verifica-se
se ele tem um único filho a esquerda (não tem filho a direita), como as verificações
são falsas, resta que o nó possui dois filhos (esquerda e direita), portanto, vai para o
próximo nível da árvore pela subárvore da esquerda e percorre a subárvore da direita
até encontrar um nó que não possua filhos a direita (ramo a direita seja nulo). Quando
isso ocorrer será trocado o valor do nó a ser excluído com o nó encontrado (maior
número entre os menores) conforme é mostrado na Figura 16-c e chama-se a função
novamente passando por parâmetro a subárvore à esquerda para excluir o nó, que
nesse caso, irá percorrer novamente a subárvore até encontrar o nó contendo o valor
6 e como dessa vez ele é uma folha, será liberado o espaço de memória e atribuído
nulo no nó, conforme mostra a Figura 16-d.
Em seguida, na linha 94 é solicitado a exclusão do valor 7, como o nó não é
vazio, é verificado se é menor, também não é, por isso, é verificado se é maior e por
isso é chamada a função recursivamente para a subárvore a direita. Na segunda
chamada da função agora com o nó atual com o valor 9, é verificado que não é vazio,
menor ou maior, desta forma, o nó contém o valor que deve ser excluído, portanto, é
verificado se não é uma folha, como o nó não é, em seguida, verifica-se se contém só
um filho a direita e neste caso é verdadeiro, com isso, salva-se o endereço do nó atual
em um ponteiro temporário, na sequência, o ponteiro a esquerda do nó contendo o
valor 6 (o nó atual da função), recebe o endereço da subárvore a esquerda (nó
contendo o valor 7), conforme mostra a Figura 16-e. Para finalizar, é liberado o espaço
em memória do endereço do ponteiro temporário, excluindo o nó que continha o valor
9, conforme mostra a Figura 16-f.
Ao final do código, na linha 95 é mostrado o texto “Arvore com 5 nos:” e pula-
se uma linha. Na sequência, chama-se o procedimento imprime_ordem novamente,
mas agora com os valores 10, 6 e 9 removidos, por isso, será mostrado na tela os
valores 1, 3, 4, 5 e 7 em cada linha, pois a árvore se encontra com a estrutura
mostrada na Figura 16-f.
Na última linha, mais especificamente, linha 97, é chamado o procedimento
libera que está liberando o espaço de todos os nós da árvore por meio do método
recursivo.

Saiba mais
As árvores binárias podem ser implementadas facilmente utilizando bibliotecas em algumas
outras linguagens. No Python, por exemplo, utilizando a biblioteca binarytree é possível usar a
classe bst (binary search tree) que contém os atributos value (valor), left (esquerda), right
(direita) e as suas operações de inorder (imprime em ordem), height (altura), size (quantidade
de nós), entre outros.

Você também pode gostar