Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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.
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.
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.
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.
(a) (b)
arv arv
4 4
1 1 7
(c) (d)
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.
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.
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.
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.
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.
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.