Você está na página 1de 30

PESQUISA, ORDENAÇÃO E TÉCNICAS DE

ARMAZENAMENTO
UNIDADE 4 - TÉ CNICAS DE PESQUISAS
SEQUENCIAL E BINÁ RIA

Keila Barbosa Costa


Introdução
A busca por uma palavra-chave ou por elementos, valores ou dados específicos (informaçõ es) é a base de
muitos aplicativos de computação. Serve para visualizar o saldo de uma conta bancária a partir de um
mecanismo de pesquisa na internet, por exemplo, ou para procurar um arquivo no computador. Este,
inclusive, lida com muitos dados, por isso, precisamos de algoritmos eficientes para a pesquisa.
No entanto, que tipos de algoritmos podem ser eficientes? Como os computadores precisam buscar
informaçõ es em coleçõ es de dados muito grandes, é essencial usar o algoritmo certo para pesquisar, mas
como definir o algoritmo adequado para cada situação? Será que podemos aplicar métodos de pesquisas
binária ou sequencial com os dados organizados de qualquer forma?
A pesquisa é o processo de encontrar o elemento em determinada lista. Neste processo, verificamos que o
item está disponível ou não. A pesquisa está em toda parte, desde encontrarmos o endereço de uma pessoa até
um nú mero de telefone em uma lista telefô nica. Neste contexto, ao longo desta ú ltima unidade, iremos
explorar algoritmos comuns que são usados ​para procurar dados em computadores.
Aprenderemos sobre pesquisa binária em vetores e pesquisa sequencial. Veremos a respeito das
particularidades de cada uma e como são aplicadas no dia a dia dos profissionais da área. Também
estudaremos a respeito das tabelas de dispersão e das árvores de busca.
Vamos, então, nos aprofundar a respeito desses assuntos? Acompanhe!

4.1 Técnicas de pesquisas


Podemos definir como dado um conjunto de elementos, em que cada um é identificado por uma chave. O
objetivo da pesquisa é localizar, dentro desse conjunto, o elemento que corresponde a uma chave específica.
Assim, uma tarefa bastante importante, hoje — não só na computação como também em outras áreas —, é a
pesquisa de informação contida em coleçõ es de dados. De forma geral, é desejado que o procedimento seja
realizado de forma que não tenha a necessidade de se verificar toda a coleção (VIANA; CINTRA; NOBRE 2015).
A busca é muito comum na área da computação, sendo que podemos usar métodos e estruturas de dados para
se encontrar o elemento esperado. A busca em vetor, por exemplo, pode ser feita por índice ou valor.
A busca realizada pelo índice é considerada uma busca direta, ou seja, vai direto na posição da memó ria.
Podemos tomar como exemplo o quadro a seguir com o vetor “nome”. Passando no índice 2, teríamos o valor
“Khyonara”. 

Quadro 1 - Exemplo de quadro com vetores

#PraCegoVer: A figura mostra uma tabela de duas linhas e 4 colunas. Na primeira linha, da esquerda para a
direita, temos os valores: 0, 1, 2, 3, 4. Na segunda linha, da esquerda da direita, temos os valores: Keila, Arthur,
Khyonara, Berenice, Aparecido.
Agora, vamos imaginar que não sabemos onde o valor “Khyonara” está. Neste caso, temos que verificar todos
os elementos do vetor até encontrá-lo.
Portanto, para realizar a busca por um valor, temos dois métodos a serem considerados: realizamos uma
busca sequencial ou uma busca binária.
A busca sequencial percorre todas as posiçõ es do vetor, verificando uma a uma, até achar o valor desejado
ou simplesmente chegar ao final sem achá-lo. Já na busca binária, o vetor é dividido ao meio e a busca é
realizada apenas em uma das metades.
Vamos entender melhor sobre os dois métodos a partir de agora. No pró ximo item, ainda mostraremos
exemplos de como o processo é realizado dentro da computação. 

4.1.1 Busca sequencial


De acordo com Viana, Cintra e Nobre (2015), a busca sequencial é definida como a procura por um valor x em
um vetor L. Inspecionando em sequência as posiçõ es de L a partir da primeira posição, podemos encontrar o
x. Ao encontrar o elemento procurado, podemos dizer que a busca obteve sucesso. Caso contrário, se
chegarmos à ú ltima posição sem encontrarmos a posição desejada, concluímos que ela não ocorre no vetor L.
Vamos imaginar o mesmo vetor “nome”, em que desejamos encontrar o nome “Khyonara”. Como isto pode ser
realizado? Comparando elemento a elemento, ou seja, o valor que está no índice pelo valor a ser procurado.

Figura 1 - Exemplo de busca sequencial

#PraCegoVer: A figura mostra um exemplo de busca sequencial. Primeiramente é mostrado um vetor de 4


posiçõ es com os seguintes valores: Keila, Arthur, Khyonara, Berenice, Aparecido.

Em seguida, mostra-se um passo a passo para encontrar o nome Khyonara. O passo a passo é mostrado
através de uma série de comparaçõ es. As comparaçõ es são:

1 comparação: Keila == Khyonara? Não


2 comparação: Arthus == Khyonara? Não
3 comparação: Khyonara == Khyonara? Sim
Achou o nome do índice 2.

Por fim, mostrasse outro passo a passo. Desta vez, o objetivo é encontrar o nome José. O passo a passo
também é mostrado através de uma série de comparaçõ es. As comparaçõ es são:

1 comparação: Keila == José? Não


2 comparação: Arthur == José? Não
3 comparação: Khyonara == José? Não
4 comparação: Berenice == José? Não
5 comparação: Aparecido == José? Não

Achou o nome do índice 2.

De acordo com a figura anterior, temos a primeira comparação realizada. Nela, percorremos o valor que está
na posição 0. Ele é igual ao valor que estamos procurando (Khyonara)? Na verdade, não!
Incrementando o índice e realizando a mesma verificação, podemos dizer que o valor que está na posição ou
no índice 1 é o mesmo que estamos procurando? Arthur é igual a Khyonara? Também não, certo?
Incrementando novamente o índice e realizando mais uma comparação, podemos dizer que o índice 2 é igual
ao valor que estamos procurando? Khyonara é igual a Khyonara? Sim! Assim, encontramos nosso elemento a
partir da posição do índice que está no valor buscado.
Agora, imagine que iremos procurar o nome José. Perceba que ele não se encontra no vetor que estamos
analisando, por isso, o algoritmo irá parar de executar quando chegar ao final, retornando e informando que
não foi encontrado o nome desejado.
No entanto, imaginemos que o vetor tenha mais de um milhão de posiçõ es, sendo que o valor desejado não se
encontra no vetor. Será necessário, então, realizar um milhão de verificaçõ es para se descobrir que o valor não
se encontra no vetor a ser verificado. Isto ocorre com frequência na busca sequencial, sendo que, para
resolver o problema de o algoritmo ter que verificar elemento por elemento, podemos usar a busca binária.
Na busca binária, o vetor precisa estar ordenado. A mesma ló gica serve para nú meros, textos ou letras. Assim,
agora que você já conhece o processo de como fazer uma busca sequencial, iremos conhecer como se dá a
busca binária. Vejamos!

4.1.2 Busca binária


A busca binária só funciona em vetores que estejam ordenados. Isto porque ela divide sucessivamente o vetor
ao meio e procura apenas em uma das metades, ou seja, o algoritmo é executado até encontrar o valor ou
posição.
Vamos ver um exemplo com a figura a seguir. Observe com atenção!
Figura 2 - Exemplo de busca binária

#PraCeoVer: A figura mostra um exemplo de busca binária. Inicialmente é mostrado um vetor de 12 posiçõ es
com os seguintes valores: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24. Abaixo do vetor tem duas frases destacando
duas posiçõ es do vetor. As frases são respectivamente: Posição inicial = 0 e Posição final = 11.

Em seguida, mostra-se o mesmo vetor. Contudo, mudou-se as frases que indicam as posiçõ es em destaque.
Desta vez, temos 3 frases, que são, respectivamente: Posição inicial = 6, Meio = 8 e Final = 11.

Depois disso, mostra-se o mesmo vetor novamente. Desta vez, houve outra alteração nas frases que destacam
algumas posiçõ es do vetor. As frases são, respectivamente: Posição inicial = 9, Meio = 10 e Final = 11.

Por fim, mostra-se novamente esse mesmo vetor com uma ú nica frase destacando uma ú nica posição. A frase
diz: Posição inicial = 20.

Imagine o vetor ordenado na figura anterior. Pretendemos procurar o elemento 20, por isso, a primeira coisa
que o vetor irá fazer é descobrir a posição inicial (0) e, depois, a posição final (11). Será realizado o seguinte
cálculo:
 
meio = (posiçaoInicial + posicaoFinal) / 2
meio = (0 + 11) / 2
meio = 5.5 (Arredondar para o inteiro mais pró ximo  5)
 
Temos, assim, que a quinta posição será a posição do meio, com valor igual a 12.
Encontrada a posição do meio, será realizada a verificação a seguir:
 
Se (meio = numeroProcurado) (18 = 20)
Achou o valor
Do contrário
Se (numeroProcurado < meio) (20 < 18)
posiçãoFinal = meio - 1
Do contrário
posiçãoInicial = meio + 1 // Realizar novamente a posição inicial.
 
Dessa forma, podemos verificar o nú mero procurado, ou seja, se o elemento do meio é igual ao nú mero
procurado (12 = 20). Como a afirmação é falsa, segue-se, então, para 20 < 12. Como a afirmação também não é
verdadeira, o vetor irá andar com a posição inicial.
Como a posição inicial era 0, agora ele vai passar a ser: posiçãoInicial = meio + 1 = 5 + 1 = 6), então, recebe a
posição 6. Perceba que a posição 6 tem o valor 14.
A parte do vetor com valor menor que 12 (< posição 5) foi desconsiderada, pois, como o vetor está ordenado,
iremos considerar apenas da posição 6 até a 11.
Com a posição inicial 6 e a posição final 11, é realizado um cálculo para obtermos a posição meio, como
vemos a seguir, até se alcançar a posição desejada. No nosso caso, trata-se da posição 9. Observe:
 
meio = (posiçaoInicial + posicaoFinal) / 2
meio = (6 + 11) / 2
meio = 8.5 (inteiro 8) vetor formado por nú meros inteiros.
 
Realizando a verificação, temos:
 
Se (meio == numeroProcurado) (12 = 20)
Achou o valor
Do contrário
Se (numeroProcurado < meio) (20 < 12)
posiçãoFinal = meio - 1
Do contrário
posiçãoInicial = meio + 1
 
Assim, foram necessárias muitas verificaçõ es até se encontrar o elemento desejado, porém, se fossemos usar
a verificação da busca sequencial, seriam necessárias ainda mais verificaçõ es. Isto é, para a busca sequencial,
teríamos que realizar 10 verificaçõ es, enquanto a busca binária realizou apenas três.

VAMOS PRATICAR?
De acordo com o que vimos, imagine o vetor ordenado a seguir. Pr
elemento 22 usando a busca binaria.
#PraCegoVer: A figura mostra um vetor de 10 posiçõ es. Os valores armazenados nesse vetor, são,
respectivamente: 3, 7, 9, 11, 12, 14, 15, 22, 23 ,25.
Agora que já entendemos a respeito das buscas sequencial e binária, no tó pico a seguir, veremos com mais
detalhes a respeito das tabelas de dispersão, também conhecidas como tabelas de espalhamento ou tabelas de
hashing. Acompanhe!

4.2 Tabelas de dispersão


Um paradigma muito comum no processamento de dados envolve o armazenamento de informaçõ es em uma
tabela e, posteriormente, a recuperação dos dados guardados. Por exemplo, considere um banco de dados de
registros de carteira de trabalho: ele contém um registro para cada carteira de trabalho emitida, sendo que,
dado o nú mero da carteira de trabalho, podemos procurar as informaçõ es associadas a ele.
Normalmente, o banco de dados compreende uma coleção de pares e valores. As informaçõ es são recuperadas
do banco a partir de uma determinada chave. No caso do banco de dados da carteira de trabalho, a chave é o
nú mero da carteira de trabalho. Já no caso de uma tabela de símbolos, por exemplo, a chave é o nome do
símbolo.
Viana, Cintra e Nobre (2015) nos explicam que podemos definir as tabelas de dispersão, também conhecidas
como tabelas de espalhamento ou de hashing, como aquelas que armazenam uma coleção de valores, sendo
que cada valor está associado a uma chave. As chaves precisam ser todas diferentes, pois são usadas para
mapear os valores das tabelas.
As vantagens da tabela de dispersão é que ela pode ser usada como índice, porém a grande vantagem está em
se ter uma operação cujo acesso é direto. Isto é, não será preciso fazer um percurso em uma árvore ou
comparar registro. Ainda de acordo com Viana, Cintra e Nobre (2015), outra vantagem é o curso da operação
O(1) acesso, em que a determinação da posição onde o registro será armazenado virá de uma função hashing.
A ideia essencial por trás de uma tabela de dispersão é que todas as informaçõ es são armazenadas em uma
matriz de tamanho fixo. O hashing é usado para identificar a posição em que um item deve ser armazenado.
Quando acontece uma colisão, o item em colisão é armazenado em outro lugar na matriz (VIANA; CINTRA;
NOBRE, 2015).
Os tipos de hashings podem ser divididos em. Clique no recurso a seguir.

Hashing Em que é permitido armazenar um conjunto de informaçõ es de tamanho limitado.


fechado

Hashing Em que é permitido armazenar um conjunto de informaçõ es de tamanho ilimitado.


aberto

A função de hashing será responsável por converter a chave em um índice de alocação. Vejamos um exemplo
de função de dispersão:
h(chave)  endereço
Função: h(x) = x mod 7, em que x é a chave e h(x) é o endereço
h(4315)  615, em que 4315 é a chave e 615 é o endereço

VAMOS PRATICAR?
De acordo com a funçã o de hashing, encontre o endereço para a chave h
Adote a fórmula “Funçã o: h(x) = x mod 5”.

Assim, temos que a função hashing vai ditar o desempenho do algoritmo, trazendo o ponto em que devemos
armazenar o registro. O segredo está na qualidade da função hashing, ou seja, quanto melhor for a função,
mais dispersos os registros serão.
Além disso, o hashing funciona da seguinte forma: a chave D1 passa pela função que é convertida no índice 1,
enquanto a chave D2 passa pela função que vai ser convertida no índice 2. Veja o esquema a seguir para
compreender melhor.

Figura 3 - Função hashing

#PraCegoVer: A figura mostra um exemplo de uma função de hashing. No centro da imagem é mostrado um
círculo contendo o texto: Função hashing. Conectado a esse círculo tem mais outros 4 círculos. Um dos
círculos está posicionado no canto superior esquerdo e possui o texto "D1". Outro círculo está posicionado no
canto superior direito e possui o texto "[1]". O terceiro círculo está posicionado no canto inferior esquerdo e
possui o texto "D". Por fim, o ú ltimo círculo está posicionado no canto inferior direito e possui o texto "[2]".

Agora, vejamos um exemplo de como a função de hashing funciona em uma tabela de dados. Observe o quadro
na sequência.

Quadro 2 - Processo da função hashing

#PraCegoVer: A figura mostra uma tabela para descrever a função de hashing. A tabela possui 8 linhas e 4
colunas.

Na primeira linha, temos a colunas (1) com valor "Índice", coluna (2) com valor "Chave", coluna (3) com o
valor "Valor", coluna (4) com o valor "Função: h(x) = x mod 7".

Na segunda linha, temos a coluna (1) com o valor "0".

Na terceira linha, temos a coluna (1) com o valor "1".

Na quarta linha, temos a coluna (1) com o valor "2", coluna (2) com o valor "30", coluna (3) com o valor
"Khyonara", coluna (4) com o valor "h(x) = x mod 7 = 30/7".

Na quinta linha, temos a coluna (1) com o valor "3", coluna (2) com o valor "45", coluna (3) com o valor
"Keila", coluna (4) com o valor "h(x) = x mod 7 = 45/7".

Na sexta linha, temos a coluna (1) com o valor "4".

Na sétima linha, temos a coluna (1) com o valor "5", coluna (2) com o valor 40, coluna (3) com o valor
"Arthur", coluna (4) com o valor "h(x) = x mod 7 = 40/7".

Observe que o valor 30 não está na posição 0, mas, sim, na posição 2, devido à fó rmula, ou seja, h(x) = x mod
7 = 30/7 ˜= 4,2 . Como o restante é aproximadamente 2, temos  para a chave 30 o lugar de alocação para a
posição 2. Da mesma forma procede para as demais chaves.
Agora, digamos que desejamos incluir uma nova chave que também na divisão com 7 gere um resto 2. A este
caso damos o nome de colisão ou hashing fechado (hash overflow). 

VOCÊ SABIA?
O hash overflow é quando tentamos inserir um elemento-chave na tabela de
dispersã o, mas a tabela já se encontra cheia, invalidando essa inserçã o. Neste caso,
dizemos que aconteceu um estouro de tabela de dispersã o.

A colisão acontece quando determinado índice (posição no vetor) gerado por uma chave se encontra alocado
por um registro. Isto é, quando o valor de uma chave ganha uma posição que já está cheia. Vejamos um
exemplo na figura a seguir.
Figura 4 - Processo de hashing fechado

#PraCegoVer: A imagem mostra o processo de hashing fechado. Inicialmente mostra-se a palavra


"chave|informação" que possui uma seta conectando com a palavra "Função" e por sua vez, possui uma seta
apontando para a palavra "função". Abaixo desta palavra está escrito "vetor com as posiçõ es".

Em seguida, é mostrado uma tabela com 13 linhas e 4 colunas. Enumerando cada linha destas tabelas temos:

Linha 1: Chave, Resto, Adote: h(x) = x mod m, 84, 0


Linha 2: 47, 5, h(47) = 47 mod 14 = 5, 70*, 1
Linha 3: 9, 9, h(9) = 9 mod 14 = 9, 25*, 3
Linha 4: 75, 5, …, 47, 4
Linha 5: 16, 2, …, 47, 4
Linha 6: 24, 10, …, 75, 5
Linha 7: 52, 10, …, 34*, 6
Linha 8: 84, 0, …, 12*, 7
Linha 9: 70, 0, …, , 8
Linha 10: 10, 10, …, 9,9
Linha 11: 25, 11, …, 24, 10
Linha 12: 34, 6, …, 52*, 11
Linha 13: 12, 12, …., h(12), 12 mod 14 = 12, 10*, 12

Na figura anterior, temos o esquema de como funciona o hashing fechado, em que temos o dado com a chave
que é convertida pela função, em um índice no qual será alocada a informação.
Podemos observar que nas chaves 24 e 52 acontece uma colisão, pois as duas têm um resto 10, ou seja,
precisam ser alocadas na mesma posição 10. Porém, a posição já tem uma chave alocada que chegou primeiro
(24). Neste caso, o que fazemos com a chave que precisa ser alocada, sendo que sua posição já está ocupada
por outra chave?
O 24 já está alocando a posição 10, então, a chave 52 vai procurar a pró xima posição vazia, que seria a 11.
Portanto, a chave 52 ficará na posição 11 com asterisco (*), deixando claro que se trata de uma chave
deslocada. Na implementação real, adotamos que cada entrada H[i] da tabela é uma lista, cujo elemento têm
hash code i. Para inserir um elemento na tabela, computamos o hash code i e inserimos o elemento na lista
ligada à H[i].
A inserção é realizada seguindo a ordem de cima para baixo na tabela, sendo que, ao final da lista, retorna-se
para o início, seguindo o ciclo novamente.
A chave deslocada só sai da posição também deslocada se for para ir à sua respectiva posição, de acordo com
a função.
A técnica de hashing aberto, por outro lado, denota que, em uma tabela, cada posição possui um ponteiro
que dá para uma lista encadeada, sendo que esta contém todas as chaves mapeadas por uma função hashing na
posição (VIANA; CINTRA; NOBRE, 2015). Aliás, na técnica aberta, não existe limite de chaves que podem ser
armazenadas na tabela.
A primeira estratégia, comumente conhecida como hashing aberto ou encadeamento separado, é manter uma
lista de todos os elementos com hashing no mesmo valor. Vejamos um exemplo concreto na tabela a seguir,
em que é possível observar na posição 10 duas colisõ es. Assim, foram adicionados dois campos extras para
alocar as posiçõ es.
Tabela 1 - Processo de hashing aberto

#PraCegoVer: A figura mostra o processo de hashing aberto. O processo é descrito através de uma tabela
contendo 13 linhas e 8 colunas. Enumerando cada linha da tabela temos:

Linha 1: Chave, Resto, adote: h(x) = x mod m, 0, -> , 84, 70,


Linha 2: 47, 5, h(47) = 47 mod 14 = 5, 1
Linha 3: 9, 9, h(9) = 9 mod 14 = 9, 2, ->, 16
Linha 4: 75, 5, …, 3
Linha 5: 16, 2, …, 4,
Linha 6: 24,10, ..., 5, ->, 47, 75
Linha 7: 52, 10, …, 6, ->, 34
Linha 8: 84, 0, …, 7
Linha 9: 70, 0, …, 8
Linha 10: 10, 10, …, 9, -> , 9
Linha 11: 25, 11, …, 10, ->, 24, 52, 10
Linha 12: 34, 6, …, 11, ->, 25
Linha 13: 12, 12, h(12) = 12 mod 14 = 12, 12, -> 12

Para realizar uma localização, usamos a função hash para determinar qual lista percorrer. Em seguida,
percorremos a lista da maneira normal, retornando à posição em que o item foi encontrado. Para executar uma
inserção, percorremos a lista apropriada para verificar se o elemento já está no lugar. Se são esperadas
duplicatas, geralmente um campo extra é mantido e este seria incrementado no caso de uma correspondência
(VIANA; CINTRA; NOBRE, 2015). Se o elemento for novo, ele será inserido na frente da lista ou no final. Este é
um problema fácil de resolver enquanto o có digo está sendo gravado. À s vezes, novos elementos são
inseridos na frente da lista, pois é conveniente e também porque frequentemente acontece que os elementos
inseridos recentemente têm maior probabilidade de serem acessados ​em um futuro pró ximo.

VAMOS PRATICAR?
Usando a tabela a seguir, realize a localizaçã o das posições das chaves u
funçã o hash. Em seguida, percorra a lista da maneira normal, retorn
posiçã o em que o item foi encontrado. Em caso de colisã o, adote o proc
hashing aberto para resolver o conflito.

#PraCegoVer: A figura mostra uma tabela com 7 linhas e 8 colunas. Enumerando cada linha da tabela temos:

Linha 1: Chave, Resto, Adote: h(x) = mod 3 , 0,,,,,


Linha 2: 50, , Adote: h(x) = mod 3 , 1,,,,,
Linha 3: 23, , Adote: h(x) = mod 3 , 2,,,,,
Linha 4: 45, , Adote: h(x) = mod 3 , 3,,,,,
Linha 5: 17, , Adote: h(x) = mod 3 , 4,,,,,
Linha 6: 9, , Adote: h(x) = mod 3 , 5,,,,,
Linha 7: 37, , Adote: h(x) = mod 3 , 6,,,,,

Assim, o hashing aberto é uma estratégia em que nenhum dos objetos é realmente armazenado na matriz da
tabela de hashing. Em vez disto, ele é armazenado em uma lista separada da matriz interna da tabela. 

4.3 Árvores de buscas balanceadas


Uma árvore de pesquisa binária balanceada mantém automaticamente sua altura pequena (garantida como
logarítmica) para uma sequência de inserçõ es e exclusõ es. Conforme Viana, Cintra e Nobre (2015), essa
estrutura fornece implementaçõ es eficientes para dados abstratos, como matrizes associativas.
Diferentemente das listas, em que as informaçõ es se encontram em uma sequência; nas árvores, esses dados
são representados de forma hierárquica, sendo que cada nó possui um conteú do (VIANA; CINTRA; NOBRE,
2015).

VOCÊ QUER VER?


Uma á rvore biná ria é composta de nós, sendo que cada nó conté m uma referê ncia
"esquerda", uma referê ncia "direita" e um elemento de dados. O nó superior da á rvore
é chamado de “raiz”. Para compreender melhor o funcionamento de uma á rvore
biná ria, sugerimos que você assistia ao vídeo disponível no link:
https://youtu.be/PgZflufXGUU. Com ele, será mais fá cil identificar os pontos
apresentados.

Nas árvores de busca balanceada, as chaves alocadas são mantidas ordenadas, permitindo que a operação de
busca seja realizada, percorrendo-se um ramo da árvore, desde a base até chegar ao início (VIANA; CINTRA;
NOBRE, 2015).
As árvores de pesquisa são como um método para armazenar dados, de modo a oferecer suporte para as
operaçõ es de inserção rápida, pesquisa e exclusão. O principal problema das árvores de busca é o desejo que
elas sejam equilibradas para que as pesquisas possam ser executadas rapidamente. No entanto, não é
necessário que elas sejam perfeitas, visto que seria muito caro de manter para inserir ou excluir um novo
elemento.
Nesse contexto, vários algoritmos foram desenvolvidos para a construção de árvores de busca que
permanecem equilibradas.

Á
4.3.1 Árvore AVL
A árvore AVL é uma árvore binária que vai seguir as mesmas regras para inserção, busca e remoção de
elementos, bem como adicionar tais regras a métodos para se manter o equilíbrio da árvore. Ela é muito
balanceada: em suas inserçõ es e exclusõ es, procura-se executar uma rotina de balanceamento em que as
alturas das sub-árvores sejam pró ximas.

VOCÊ O CONHECE?
Georgy Adelson era um matemá tico e cientista de computaçã o. Nascido em Samara,
Adelson foi originalmente educado como um matemá tico puro. Seu primeiro trabalho,
com o colega e eventual colaborador de longa data, Alexander Kronrod, em 1945,
ganhou um prê mio da Sociedade Matemá tica de Moscou. Ele começou a trabalhar em
inteligê ncia artificial e outros tópicos aplicados no final da dé cada de 1950. Junto com
Evgenii Landis, inventou a á rvore AVL em 1962. Esta foi a primeira estrutura de dados
da á rvore de pesquisa biná ria balanceada já conhecida.

Conforme nos explicam Viana, Cintra e Nobre (2015), as operaçõ es básicas, como busca, inserção, remoção e
outras, levam um tempo proporcional ao nú mero de níveis da árvore binária de busca. Isto é, a quantidade de
operaçõ es para busca, inserção e remoção, no pior dos casos, depende de quantos níveis existem na árvore.
Dada esta observação, podemos concluir que é desejado manter a árvore sempre com a menor quantidade de
níveis possível para que as operaçõ es básicas não custem tanto tempo. 
Vejamos um exemplo em que a inserção dos nú meros se encontra em sequência (10, 11, 12, 13, 14, 15, 16, 17
e 18), ou seja, foi inserido nú mero por nú mero de forma sequencial na árvore. Observe a figura a seguir.
Figura 5 - Distribuição de árvore sequencial

#PraCegoVer: A figura mostra duas representaçõ es de árvores. Na primeira representação temos uma árvore
desequilibrada em que todos os nó s estão conectados do seu lado direito. A sequências de nó s é: 10, ao lado
direito deste tem o nó 11, ao lado direito deste tem o nó 12, ao lado direito deste tem o nó 13, ao lado direito
deste tem o nó 14 e ao lado direito deste tem o no 15.
Em seguida, mostra-se o desenho de uma árvore ideal em que cada nó possui dois filhos, um filho ou nenhum
filho.

Neste caso, para  elementos, a árvore teria  níveis, resultando em uma péssima distribuição. O ideal seria que
os nó s, conforme sejam inseridos, também sejam rearranjados para manter um equilíbrio. Assim, o ideal seria
manter a mesma quantidade de níveis na subárvore da direita e na subárvore da esquerda.
A questão é: como fazer para garantir esse equilíbrio? A resposta vai estar nas chamadas rotaçõ es. Podemos,
inclusive, dividir o problema em duas partes: como detectar o desequilíbrio e como corrigir o desequilíbrio.
Vamos começar entendendo como detectar o desequilíbrio.
O equilíbrio de uma árvore de busca é medido subtraindo o nú mero de níveis na subárvore da esquerda do
nú mero de níveis na subárvore da direita.
Vejamos um exemplo a seguir, em que temos uma árvore qualquer e podemos observar cada um dos nó s, bem
como contar quantos níveis existe à esquerda e à direta, subtraindo esse nú mero.

Figura 6 - Desequilíbrio de uma árvore binária

#PraCegoVer: A figura destaca o desequilíbrio de uma árvore binária é uma propriedade do nó . Para
demonstrar esse fato está sendo mostrado uma árvore em que um nó possui altura esquerda com valor 2 e
altura direita com valor 2. Sendo assim causando o desequilíbrio da subárvore.

Pode-se dizer que a árvore está desequilibrada apenas quando o nú mero for maior que 1 ou menor que -1.
Assim, iremos tolerar nú meros de equilíbrio de 0, 1 e de -1, pois podemos ter um nú mero ímpar de nó s, ou
seja, em alguns casos, não conseguiremos fazer a árvore ter um equilíbrio completamente nulo.
Uma vez detectado o desequilíbrio na árvore, o pró ximo passo é entender como corrigi-lo, sendo este feito por
meio das rotaçõ es.
VOCÊ QUER LER?
Um jogo educacional digital, denominado DEG4Trees (Digital Educational Game four
Trees), foi construído para apoiar o ensino de estrutura de á rvores biná rias de buscas
e á rvores AVL. No jogo, é preciso observar as propriedades das estruturas de dados e
interagir com elas para atingir os objetivos. Para saber mais, sugerimos a leitura do
artigo “DEG4Trees: um jogo educacional digital de apoio ao ensino de estruturas de
dados”, de Weider Alves Barbosa, Isabella de Freitas Nunes, Ana Carolina Gondim
Inocê ncio, Thiago Borges de Oliveira e Paulo Afonso Parreira Jú nior, disponível em:
https://www.researchgate.net/profile/Weider_Alves_Barbosa2/publication/2657866
05_DEG4Tree_Um_Jogo_Educacional_Digital_de_Apoio_ao_Ensino_de_Estruturas_de_Dad
os/links/55fc6c1808ae07629e0d3628.pdf

Existem quatro tipos de rotaçõ es, sendo dois tipos primitivos e dois compostos de rotaçõ es: rotação à
esquerda, rotação à direita, rotação dupla à esquerda e rotação dupla à direita. Clique no recurso a seguir para
conhecer a rotação à esquerda e rotação à direita.

Rotação à esquerda

A rotação à esquerda é como o pró prio nome sugere. Os primeiros nó s que estão na subárvore da
direita passam para a esquerda, fazendo com que o filho da direita se torne a nova raiz. Também
temos o caso particular em que o filho da direita já possui o filho da esquerda, com todos os
elementos da subárvore da direita maiores que a raiz (x > 1).

Rotação à direita

Já a rotação à direita vai funcionar de forma análoga, seguindo a mesma ló gica da rotação à
esquerda. Nela, temos uma diagonal para o outro lado, ou seja, o filho da esquerda vira a nova raiz
(2 = raiz) e o filho da direita de 2 será o filho da esquerda de 3. Depois, 3 se tornará o filho da
direita de 2. 

Veja a figura a seguir para entender melhor.


Figura 7 - Tipos de rotação para árvore AVL

#PraCegoVer: A figura mostra alguns exemplos de rotação em uma árvore AVL.

No primeiro exemplo, temos uma árvore com três nó s, cada um deles conectado ao lado direito do outro. Os
valores dos nó s são: 1, 2 e 3, respectivamente. Logo em seguida, é mostrado uma rotação a esquerda em que o
nó 2 passou a ser o nó raiz e o nó 1 ficou conectado a sua esquerda e nó 3 continuou conectado a sua direita.

No segundo exemplo, mostra-se uma árvore com três nó s em que cada nó está conectado ao lado direito do
outro nó . Os valores do nó s são 1, 2 e 3 respectivamente. Depois disso, mostra-se o valor x sendo adicionado
no nó 2, o que vai obrigar a execução de uma rotação da árvore. Sendo assim, apó s a rotação, o nó 2 passou a
ser o nó raiz e do seu lado esquerdo ficou o nó 1. Do lado direito do nó 1 foi encaixado o novo nó com o valor
x. O nó 3 continuou ao lado direito do nó 2.

O terceiro e ú ltimo exemplo mostra uma árvore com 3 nó s em que cada nó está conectado ao lado esquerdo
do outro nó . Os valores dos nó s são 3, 2 e 1 respectivamente. Depois disso, mostra-se o valor x sendo
adicionado no nó 2 o que vai obrigar que aconteça uma rotação. Apó s a rotação, o nó 2 passou a ser o nó raiz,
o nó 1 ficou do seu lado esquerdo e o nó 3 continuou do seu lado direito. O novo nó com o valor x ficou
conectado ao lado esquerdo do nó 3.

Vamos tentar compreender, agora, como funciona a rotação dupla à esquerda e a rotação dupla à direita, que
são rotaçõ es duplas compostas.
Considere a seguinte situação: a rotação simples à esquerda não resolve o desequilíbrio. Para detectar isto,
basta olharmos o desequilíbrio das subárvores de onde a nova raiz vai vir (3). Temos, assim, um
desequilíbrio positivo na raiz original (1), mas na subárvore da direita percebemos um desequilíbrio negativo
(3). Para corrigir o problema, podemos adotar como solução uma rotação à direita na subárvore da direita e,
em seguida, uma rotação à esquerda na árvore original.
A rotação dupla à direita vai ser bem similar à rotação dupla à esquerda, conforme vemos na figura na
sequência.
Figura 8 - Processo da rotação dupla à direita e à esquerda, respectivamente

#PraCegoVer: A figura mostra três exemplos de rotação.

No primeiro exemplo temos uma árvore com um nó raiz com valor 1 e ao seu lado direito tem um nó com o
valor 3. Do lado esquerdo do nó 3 tem um nó com o valor 2. Ao lado do nó 1 tem o nú mero 2 indicando o
valor balanceamento do nó e a o nó 3 possui o valor –1 indicando seu balanceamento. Ao lado direito desta
árvore tem uma seta apontando para outra arvore que possui o nó 3 como nó raiz, o nó 1 conectado ao seu
lado direito e por fim o nó 2 conectado ao lado direito do nó 1.

No segundo exemplo temos uma árvore com um nó raiz com valor 1 e ao seu lado direito tem um nó com o
valor 3. Do lado esquerdo do nó 3 tem um nó com o valor 2. Ao lado direito desta árvore tem uma seta
apontando para uma outra árvore que possui o nó 1 como nó raiz. Ao lado direito do nó 1 tem um nó com o
valor 2 e ao lado direito do nó 2 tem um nó com o valor 3. Embaixo desta árvore tem um texto escrito:
"Rotação à direita na subárvore da direita". Ao lado desta segunda árvore tem uma seta apontando para uma
terceira árvore a qual contém o nó 2 como nó raiz. Do lado esquerdo do no 2 tem um nó com o valor 1 e do
lado direito tem um nó com o valor 3. Embaixo desta árvore tem um texto com a frase: "Rotação à esquerda na
árvore original".

No terceiro exemplo temos uma árvore com um nó raiz com valor 1 e ao seu lado direito tem um nó com o
valor 3. Do lado esquerdo do nó 3 tem um nó com o valor 2. Ao lado direito desta árvore tem uma seta
apontando para uma outra árvore que possui o nó 1 como nó raiz. Ao lado direito deste nó tem um nó com o
valor 2 e ao lado esquerdo desse nó tem um nó com o valor 3. Embaixo desta árvore tem um texto escrito:
"Rotação à direita da subárvore da direita". Ao lado direito dessa árvore tem uma seta apontando para uma
outra árvore com 3 nó s. O nó raiz tem o valor 3, ao lado esquerdo tem um nó com valor 1 e ao lado direito tem
um nó com valor 2.

Agora que já conhecemos a rotação dupla à esquerda e a rotação dupla à direita, como decidir qual delas
devemos usar? Para tanto, precisamos seguir um passo a passo. Clique nos ícones a seguir.

Calcular o fator de equilíbrio (Q = R - L), em que


“R” é o nú mero de níveis à direita e “L” é o
nú mero de níveis à esquerda.

Se -1 ≤ Q ≤ 1, a á rvore é equilibrada.

Se Q > 1, temos duas opçõ es: se a subá rvore da


direita tem Q < 0, entã o a rotaçã o seria dupla à
esquerda, mas, do contrá rio, seria rotaçã o à
esquerda; se a subá rvore da direita tem Q > 0,
entã o a rotaçã o seria dupla à direita, mas, do
contrá rio, seria rotaçã o à direita.
VAMOS PRATICAR?
Uma vez detectado o desequilíbrio na á rvore, o próximo passo é entend
corrigi-lo. Use o processo da rotaçã o dupla para corrigir o desequilíbrio d
a seguir.

#PraCegoVer: A figura mostra a representação de uma árvore com 3 nó s. O nó raiz possui o valor 2. Ao lado
direito do nó 2 está conectado o nó com o valor 4. Ao lado esquerdo do nó 4 está conectado o nó com o valor
3.

Como já conhecemos a respeito da árvore AVL, vamos, no pró ximo item, estudar a respeito das árvores B e B+.
Vejamos!

4.3.2 Árvores B e B+
As árvores B são uma forma de árvore de pesquisa equilibrada, baseada em árvores gerais. Um nó da árvore B
pode conter vários elementos, em vez de apenas um, como nas árvores de pesquisa binária. Isto é, em vez de
ter apenas dois filhos, ela pode ter vários.
De acordo com Viana, Cintra e Nobre (2015), as árvores B são especialmente ú teis para estruturas de pesquisa
armazenadas em discos. Estes têm características de recuperação diferentes da memó ria interna (RAM) e,
obviamente, seu acesso é muito mais lento.
Na maioria das árvores de pesquisas, como a AVL, supõ e-se que tudo esteja na memó ria principal. Contudo,
para entender o uso das árvores B, devemos pensar na quantidade de dados que não cabe na memó ria
principal. Quando o nú mero de chaves é alto, os dados são lidos do disco na forma de blocos. A ideia
principal do uso de árvores B é reduzir o nú mero de acessos ao disco.
Temos, assim, que as árvores B são uma boa combinação para armazenamento e pesquisa em disco, pois é
possível escolher o tamanho do nó para corresponder ao tamanho do cilindro. Ao fazer isto, armazenaremos
muitos membros de dados em cada nó , tornando a árvore mais plana, para que sejam necessárias menos
transiçõ es (VIANA; CINTRA; NOBRE, 2015).
Uma estrutura de árvore B é balanceada automaticamente e, geralmente, permite que um nó tenha mais de
dois filhos — para manter a árvore larga e, portanto, crescer em altura —, com várias chaves inseridas em um
nó .
Normalmente, definimos uma árvore B com “ordem b”, em que “b” é o nú mero mínimo de filhos e “2b” é o
nú mero máximo de filhos que qualquer nó pode ter.
A seguir, temos um exemplo de uma árvore B com b = 2.

Figura 9 - Exemplo de árvore B

#PraCegoVer: A figura mostra uma representação de uma árvore B. No topo da imagem temos um vetor de 3
posiçõ es com os seguintes valores: 8, 21 e 34. Tem 4 setas saindo deste vetor. Cada seta aponta para um vetor
diferente. A primeira seta aponta para um vetor com os valores; 8, 21, 34. A segunda seta aponta para um vetor
com os valores: 14. A terceira seta aponta para um vetor com o valor 23, 31. Por fim, a ú ltima seta aponta para
um vetor com o valor 45.

Na árvore da figura anterior, podemos perceber que cada nú mero inteiro é considerado uma chave separada,
ou seja, cada um teria seu pró prio nó . Assim como em outras árvores, todas as chaves na subárvore à
esquerda são menores que a chave atual, bem como todas as chaves na subárvore à direita são maiores. Por
exemplo, começando na chave 8, as chaves 3, 4 e 7 são menores, enquanto a chave 14 é maior. 
Observe, também, que, como o nú mero máximo de filhos é 2 * 2 = 4, um nó pode armazenar apenas três
chaves, pois os indicadores para filhos são armazenados entre as chaves e nas bordas. 
Para implementar a indexação dinâmica em vários níveis, geralmente são utilizadas as árvores B e B+. A
desvantagem da árvore B usada para indexação é que ela armazena o ponteiro de dados (ponteiro para o bloco
de arquivos do disco que contém o valor da chave), correspondente a um valor de chave específico,
juntamente com o valor de chave no nó de um B. Esta técnica reduz bastante o nú mero de entradas que podem
ser empacotadas em um nó de uma árvore B, contribuindo para o aumento do nú mero de níveis e do tempo de
pesquisa de um registro.
A árvore B+ elimina essa desvantagem, armazenando ponteiros de dados apenas nos nó s das folhas da árvore.
Assim, a estrutura é bem diferente da dos nó s internos da árvore B+.
Vejamos algumas propriedades das árvores B e B+. Clique nos ícones a seguir.

Todas as folhas estã o no mesmo nível.

Uma á rvore B é definida pelo termo grau mínimo


“t”, sendo que seu valor depende do tamanho do
bloco de disco.

Todo nó , exceto a raiz, deve conter pelo menos


chaves “t - 1”.

A raiz pode conter, no mínimo, uma chave.

Todos os nó s, incluindo raiz, podem conter, no


má ximo, “2t - 1” chaves.

O nú mero de filhos de um nó é igual ao nú mero


de chaves, mais 1.

Todas as chaves de um nó sã o classificadas em


ordem crescente.

O filho entre duas chaves k1 e k2 contêm todas as


chaves no intervalo de k1 e k2.


A á rvore B cresce e diminui a partir da raiz,
diferente da á rvore de pesquisa biná ria.

Todo nó do tipo folha possui a mesma


profundidade entre eles e o nó da raiz.

Como outras á rvores de pesquisa biná ria


equilibradas, a complexidade do tempo para
pesquisar, inserir e excluir é O(Log n).

Cada nó está 50% cheio, com exceçã o o nó da


raiz.
Na figura a seguir, podemos observar um exemplo de árvore B+.

Figura 10 - Á rvore B+

#PraCegoVer: A figura mostra uma representação de uma árvore B+. Primeiramente é mostrado um vetor
com os valores 23,31. Existem três seta saindo desse vetor. Uma das setas aponta para um vetor com os
valores 8, 21. A segunda seta aponta para um vetor com o valor 23 e a terceira seta aponta para um vetor com
o valor 34.

O vetor com os valores 8, 21 também possui 3 setas apontando para outros 3 vetores. A primeira seta aponta
para um vetor com o valor 2, 4. A segunda seta aponta para um vetor com os valores 8,11 e a terceira seta
aponta para um vetor com o valor 21.
O vetor com o valor 26 possui duas setas aponta para outros dois vetores. A primeira será aponta para um
vetor com valor 23 e a segunda seta aponta para um vetor com o valor 26, 28.

O vetor com valor 34 possui 2 apontando para outros dois vetores. A primeira seta está apontando para um
vetor com o valo 31 e a segunda seta está apontando para um vetor com os valores 34 e 37.

Dessa forma, a árvore B+ é muito semelhante a árvore B. As duas se equilibram automaticamente e possuem
operaçõ es logarítmicas de inserção, localização e exclusão.
Uma árvore B+ pode ser vista como uma árvore B em que cada nó contém apenas chaves (não pares de chave-
valor) e um nível adicional é inserido na parte inferior, com folhas vinculadas.

Síntese
Chegamos ao fim de mais uma unidade de estudos! Aqui, pudemos compreender as técnicas de pesquisa
sequencial e binária, em que foi possível aprender os conceitos e a aplicabilidade de tabelas de dispersão.
Também pudemos conhecer as principais árvores de buscas e suas complexidades.
Nesta unidade, você teve a oportunidade de:

• conhecer as técnicas de pesquisas sequencial e binária;


• entender sobre as tabelas de dispersão;
• compreender a respeito das árvores de buscas balanceadas;
• identificar as técnicas de manutenção das árvores de pesquisas;
• verificar a complexidade dos algoritmos de pesquisa.

Bibliografia
BARBOSA, W. A. et al. DEG4Trees: um jogo educacional digital de apoio ao ensino de estruturas de dados.
Goiás: Universidade Federal de Goiás, 2015. Disponível em:
https://www.researchgate.net/profile/Weider_Alves_Barbosa2/publication/265786605_DEG4Tree_Um_Jogo_
Educacional_Digital_de_Apoio_ao_Ensino_de_Estruturas_de_Dados
/links/
(https://www.researchgate.net/profile/Weider_Alves_Barbosa2/publication/265786605_DEG4Tree_Um_Jogo_
Educacional_Digital_de_Apoio_ao_Ensino_de_Estruturas_de_Dados/links/)55fc6c1808ae07629e0d3628.pdf
(https://www.researchgate.net/profile/Weider_Alves_Barbosa2/publication/265786605_DEG4Tree_Um_Jogo_
Educacional_Digital_de_Apoio_ao_Ensino_de_Estruturas_de_Dados/links/55fc6c1808ae07629e0d3628.pdf).
Acesso em: 11 out. 2019.
VIANA, G. V. R.; CINTRA, G. F.; NOBRE; R. H. Pesquisa e ordenação de dados. 2. ed. Ceará: EdeuECE, 2015.

Você também pode gostar