Você está na página 1de 29

Índice ordenado em nível único e índices multiníveis

Breno Mattos dos Reis, Hallef Henrique L. dos Reis, João Isaac Máximo, João Paulo
Gonzaga Ribeiro, João Marcos de Assis
Centro Universitário Governador Ozanam Coelho - UNIFAGOC
Graduandos em Ciência da Computação
Novembro de 2022
brenomattosreis20@gmail.com, hlrtr@hotmail.com,
joaomarcosdeassis05@gmail.com, jpgonzagaribeiro@gmail.com,
j.isaacmaximo@gmail.com

Tipos de Índices Ordenados em Nível Único


A ideia por trás do índice de acesso organizado é semelhante à de um índice de livro: liste
os termos-chave no final do livro em ordem alfabética comum alistados números de
página onde cada termo aparece. Podemos pesquisar um índice para encontrar um
conjunto de endereços, neste caso, números de página, e usá-los endereços para localizar
um termo texto do livro, olhando para páginas sugeridas. A alternativa, se nenhum outro
guia foi fornecido, seria fazer uma busca lenta, palavra por palavra, em todo o texto do
livro para encontrar a palavra-chave que nos interessa, é equivalente a fazer uma busca
linear em um índice. É óbvio que a maioria dos livros possui informações
complementares, como títulos de capítulos e seções, que facilitam a localização de um
termo sem a necessidade de pesquisar o livro inteiro. No entanto, o índice é a única
indicação exata de onde cada termo é encontrado no livro.
Uma estrutura de acesso para um índice é geralmente definida com base em um
campo específico no documento chamado “campo de indexação” (ou “atributo de
indexação”) para um arquivo com uma determinada estrutura de registro composta de
muitos campos (ou atributos). Em geral, o índice armazena cada valor de campo de
indexação com uma lista de ponteiros para todos os blocos de disco contendo registros
com esse valor de campo. Os valores no indicador são organizados para podermos realizar
uma pesquisa binária. A estrutura do índice é significativamente menor do que a estrutura
de dados, tornando a pesquisa pelo índice usando a pesquisa binária razoavelmente eficaz.
A disponibilidade de vários índices nos livra da necessidade de fazer uma busca binária
em detrimento da criação de índices para os índices individuais.
Existem vários tipos de índices ordenados. Um índice primário é especificado no
campo de classificação de um banco de dados organizado de registros. Lembre-se que
para ordenar fisicamente os arquivos no disco, é utilizada uma classificação campo-chave,
e cada arquivo possui um valor único para o campo. Quando mais de um registro na base
de dados puder ter o mesmo valor para o campo de classificação, ou quando o campo de
classificação não for um campo-chave, pode ser utilizado outro tipo de índice chamado
índice clustering. Esteja ciente que um arquivo só pode ter no máximo um campo de
classificação física; como resultado, ele terá apenas um índice primário ou um índice de
clustering, não ambos. Um terceiro tipo de índice, conhecido como índice secundário,
pode ser especificado em qualquer campo diferente daquele usado para classificar um
artigo. Além de seu método de acesso primário, um documento pode ter muitos
indicadores secundários. Discutiremos esses três tipos diferentes de índices em um único
nível nas próximas três seções.

Índices Primários
Um documento estruturado com dois campos e registradores de tamanho fixo é chamado
de índice primário. O primeiro campo é o mesmo tipo de dados que o campo de
classificação, geralmente conhecido como campo de classificação primário, enquanto o
segundo campo é um ponteiro para um bloco de dados em forma de disco (um endereço
de bloco). Há uma entrada de índice (ou registro de índice) para cada bloco de dados no
banco de dados de índice. Cada entrada de índice possui valores para seus dois campos:
o valor da chave primária para o primeiro registro em um bloco e um ponteiro para esse
bloco. Discutiremos os valores desses dois campos a partir da entrada de índice por <K(i),
P(i)>.
Usamos o campo NOME como o identificador principal do artigo exibido, pois
ele serve como campo de classificação do artigo (assumindo que cada valor NOME é
único). Cada entrada no índice tem um valor NOME e um ponteiro. As três primeiras
entradas no índice são as seguintes:

<K(1) = (Aaron, Ed), P(1) = endereço do bloco 1>


<K(1) = (Aaron, Ed), P(1) = endereço do bloco 1>
<K(3) = (Alexander, Ed), P(3) = endereço do bloco 3>
O número total de entradas no índice é igual ao número de blocos de dados
organizados no arquivo de dados. O primeiro registro em cada bloco de dados é chamado
de registro inicial do bloco ou simplesmente registro inicial do bloco.
Além disso, os índices podem ser caracterizados como densos ou esparsos. Existe
uma entrada de um índice para cada valor do parâmetro de pesquisa (e, para cada registro)
na estrutura de dados. No entanto, apenas alguns dos valores de pesquisa têm entradas em
um índice arbitrário (ou não denso). Um índice primário é, portanto, um índice esparso,
pois inclui entradas de índice para cada bloco de dados no arquivo de dados e seus
registros ancestrais, em vez de uma entrada para cada valor de pesquisa (ou cada registro).
Por dois motivos, a estrutura de índice de um índice primário requer muito menos
blocos do que uma estrutura de dados. Primeiro, há menos entradas de índice do que
registros no arquivo de dados. De acordo com isso, como cada entrada de índice possui
apenas dois campos, eles geralmente são menores em tamanho do que as entradas de
dados; como resultado, cabem mais entradas de índice do que entradas de dados em um
bloco. Portanto, uma pesquisa binária em um banco de dados de índice requer menos
acesso aos blocos do que uma pesquisa binária em arquivos de dados.
Um arquivo de dados ordenado exigiria log2 b acessos a blocos. Mas, se o arquivo
de índice primário contém bi blocos, então localizar um registro com um valor de chave
de busca exige uma busca binária naquele índice e o acesso ao bloco contendo aquele
registro: um total de log2bi + 1 acesso.
Figura 1. Arquivo de Índice
Um registro cujo valor de chave primária é K está localizado em um bloco cujo
endereço é P(i), onde K(i) ≤ K < K(i + 1). Todos esses registros estão contidos no primeiro
bloco do arquivo de dados devido ao físico disposição dos registros no arquivo sobre o
campo-chave primário. Dado o valor K do valor da chave primária de um registro,
realizamos uma pesquisa binária no banco de dados para localizar a entrada apropriada
para o valor da chave I e então recuperamos o bloco de dados do banco de dados cujo
endereço é P(i).
O primeiro exemplo mostra a economia de acesso aos blocos que pode ser feita
quando um índice primário é usado para pesquisar um registro.
O primeiro exemplo ilustra a economia de acesso que pode ser:
Figura 2. Exemplo
O principal problema com um índice primário, assim como qualquer outro
trabalho ordenado, é a inclusão e exclusão de registros. Com um índice primário, o
problema surge porque, para incluir um registro em seu local adequado na estrutura de
dados, devemos não apenas mover os registros existentes para abrir espaço para o novo
registro, mas também alterar várias entradas de índice desde a mudança registros também
irão alterar o registrador-âncora de uma série de blocos. Utilizar um arquivo de overflow
desorganizado pode diminuir esse problema. Outra opção é usar uma lista ordenada para
conter os registros de overflow para cada bloco de dados no arquivo de dados. Isso é
semelhante ao método para tratar registros de estouro descritos para hashing. Para
melhorar o tempo de recuperação, os registros dentro de cada bloco e sua extensa lista de
overflow podem ser organizados. A exclusão de registros é realizada por meio do uso de
marcadores de exclusão.

Índices Clustering
Se os registros de um arquivo estiverem ordenados fisicamente de acordo com um campo
diferente de uma chave de campo, que não possui um valor único para cada registro, esse
campo é conhecido como campo de clustering (agrupamento). Para acelerar a
recuperação de registros com o mesmo valor para o campo clustering, podemos criar um
tipo diferente de índice chamado índice clustering. Esse índice difere do índice primário,
que exige um campo de classificação no arquivo de dados para ter um valor exclusivo
para cada registro.
Um arquivo ordenado com dois campos, o primeiro dos quais é um campo do tipo
clustering do arquivo de dados e o segundo é um ponteiro do tipo bloco. Há uma entrada
no índice de agrupamento para cada valor de agrupamento distinto, incluindo o valor e o
ponto para o primeiro bloco de dados no banco de dados que possui um registro com esse
valor de agrupamento. Como os registros de dados são ordenados fisicamente, ainda
surgem problemas quando os registros são incluídos ou excluídos. É prática comum
reservar um bloco inteiro (ou um grupo de blocos vizinhos) para cada valor no campo de
clustering para aliviar o problema de inclusão; Todos os registros com esse valor estão
localizados no mesmo bloco (ou grupo [cluster] de blocos), tornando a inclusão e
exclusão relativamente simples.
Outro exemplo de dispersão de índice é um agrupamento de índice, que possui
entradas para cada valor que é distinto do campo de indexação e não é um campo de
interesse definido. Como resultado, existem valores duplicados em vez de uma entrada
para cada registro de documento. De certa forma, os índices se assemelham às estruturas
de diretório usadas em hash extensivo. Ambos estão sendo procurados para identificar
um link de conexão ao bloco de dados que contém o registro desejado. Uma distinção
importante é que uma pesquisa em um índice usa os valores do próprio campo de
pesquisa, enquanto uma pesquisa em um diretório de hash usa o valor de hash, que é
determinado usando a função de hash no campo de pesquisa.

Índices Secundários
Um índice secundário fornece um meio secundário de acesso a um objeto para o qual já
existe um acesso primário. O índice secundário pode ser aplicado a um campo que seja
candidato à chave e tenha um valor único em cada registro, ou a um campo que não seja
a chave, mas tenha valores duplicados. O índice é uma matriz ordenada de dois campos.
O primeiro é o mesmo tipo de dados de algum outro campo que não seja o campo de
classificação para o arquivo de dados, que é um campo para indexação. O segundo é um
bloco ou ponto de registro. Pode haver vários índices secundários (e, vários campos de
indexação) para o mesmo arquivo.
Figura 3. Arquivo de Índice 2
Primeiro, vamos dar uma olhada em uma estrutura de acesso de índice secundário
em um campo-chave que possui um valor único para cada registrador. Às vezes, essa área
é chamada de chave secundária. Neste caso, há uma entrada para cada registro de dados
no banco de dados que contém o valor da chave secundária no registro e um link para o
bloco onde o registro está armazenado ou para o próprio registro. Assim, o indicador é
denso.
Agora nos referimos aos dois valores nos campos de entrada do indicador como
<K(i), P(i)>. Para nos permitir realizar uma busca binária, as entradas são organizadas
conforme o valor de K(i). Não podemos usar âncoras de blocos, pois os registros de dados
no arquivo de dados não são ordenados fisicamente pelos valores do campo chave
secundária. Como resultado, em vez de uma entrada para cada bloco como no caso do
índice primário, é criada uma entrada para cada registro na estrutura de dados. Uma busca
pelo registro desejado no bloco pode ser executada uma vez que o bloco apropriado tenha
sido movido para a memória principal.
Dado seu maior número de entradas, um índice secundário geralmente requer mais
espaço de armazenamento e mais tempo para pesquisar do que um índice primário. No
entanto, a melhoria no tempo de busca de um registro selecionado arbitrariamente é muito
maior em um índice secundário do que em um índice primário, pois, na ausência do índice
secundário, uma busca linear teria que ser realizada no arquivo de dados. Mesmo que o
índice primário não existisse, ainda poderíamos fazer uma pesquisa binária no documento
principal.
Figura 4. Arquivo de Índice 3
Um índice clustering com um grupo (cluster) separado de blocos para cada grupo de
registros que compartilhem o mesmo valor de campo de clustering.
Exemplo 2: Pegue a estrutura do Exemplo 1 com 30.000 registros de tamanho fixo e R =
100 bytes armazenados em um disco com tamanho de bloco B = 1,024 bytes. A estrutura
possui b = 3.000 blocos, conforme calculado no Exemplo 1. Uma busca linear no
documento precisaria de b/2 = 3.000/2 = 1.500 acessos médios a cada bloco. Suponha que
criamos um índice secundário com base em um campo do documento diferente do campo
de classificação e que seu tamanho é V = 9 bytes. Como no Exemplo 1, o ponteiro de um
bloco tem 6 bytes de tamanho,

Um índice secundário denso em um campo que não é usado para classificar um


arquivo (com ponteiros de bloco).

Além disso, podemos criar um índice secundário em um campo que não seja o
campo de palavra-chave do arquivo. Nesse caso, vários registros do arquivo podem
compartilhar o mesmo valor no campo de indexação. Existem várias opções de
implementação para este índice:
A primeira opção envolve a inclusão de várias entradas de índices com o mesmo
valor de K(i) – uma para cada registrador. Isso seria um índice denso.
A segunda opção implica ter registros de comprimento variável nas entradas do
índice e um campo de vários valores para o ponteiro. Criamos uma lista de ponteiros
<P(i,1),…, P(i, k)> na entrada de índice para K(i), um ponteiro para cada bloco contendo
um registrador cujo valor do campo de índice é igual para K(i). O algoritmo de busca
binária para o indicador precisa ser alterado de acordo em ambos os casos.
A terceira e mais popular opção envolve manter o tamanho das entradas de índice
individuais e ter uma única entrada para cada valor no campo de índice, bem como criar
um nível adicional de indireção para lidar com vários ponteiros. Nesse diagrama
equilátero, o ponteiro P(i) na entrada do índice aponta para um grupo de ponteiros de
registro, e cada ponteiro de registro nesse grupo aponta para um dos registros no arquivo
de dados com o valor K(i) na indexação campo. É usado para criar um grupo de blocos
(um cluster) ou uma lista ordenada de blocos quando um determinado valor de K(i) ocorre
com frequência nos registradores, impedindo que seus polos se agrupem em um único
bloco do disco.
Um índice secundário (com ponteiros de registro), em um campo que não é
campo-chave, implementado em um nível adicional, indireto, de forma que as entradas
de índice sejam de tamanho fixo e possuam valores de campo únicos.
Esteja ciente que o índice secundário fornece uma ordem lógica para os registros
com base no campo de indexação. Obteríamos o mesmo resultado na ordem dos campos
de indexação se acessássemos os registros na ordem das entradas no índice secundário.

Resumo

Tipos de Índices Ordenados em Multiníveis


Os esquemas de indexação que discutimos até este ponto envolvem um arquivode dados
de indexação organizado. Para localizar os pontos que levam a um bloco no disco ou um
registro (ou registros) em um arquivo que tenham um valor específico no campo de
indexação, uma pesquisa binária é aplicada ao índice. Um acesso aproximado (log2bi) a
cada bloco em um índice bi blocos é necessário para uma pesquisa binária porque cada
etapa do algoritmo reduz a parte do arquivo de dados do índice em que devemos continuar
nossa pesquisa. Usamos a função log com base 2 para esta razão.
A ideia de um índice multinível é reduzir a área da base de dados de índices onde
devemos continuar procurando um fator que divida o índice em blocos e seja maior que
2. Como resultado, o espaço de busca é reduzido significativamente mais rapidamente. O
fan-out do valor do índice multinível é chamado de valor bfri, e nos referimos a ele pelo
símbolo fo. Pesquisas de vários níveis de indicadores precisam de acesso (logfobi) aos
blocos, que é menor que o número necessário para pesquisas binárias se o fan-out for
maior que 2.
Um índice multinível considera que o arquivo subjacente do índice, que agora
consideraremos como seu primeiro nível (ou base), é um arquivo ordenado com um
valor único para cada K (i). Por causa disso, podemos criar um índice primário para o
primeiro nível; esse índice primário é conhecido como o segundo nível do índice
multinível. Podemos usar índices de bloco, pois o segundo nível é um índice primário, de
modo que contém entradas para cada bloco do primeiro nível. O fator de divisão do bloco
bfri do segundo nível e de todos os níveis subsequentes é o mesmo que o índice do
primeiro nível porque todas as entradas do índice são do mesmo tamanho. Cada um tem
um endereço de bloco e um valor de campo. Se o primeiro nível tiver entradas ri e o fator
de divisão em blocos, também conhecido como fan-out, para a equação para bfri = fo,
então o primeiro nível requer blocos, que, consequentemente, é a quantidade de entradas
r2 requeridas no segundo nível do índice.

Podemos repetir este método para alcançar o próximo nível. Existe uma entrada
para cada bloco do segundo nível no terceiro nível, que serve como um índice primário
para o segundo nível. Como resultado, o número de entradas no terceiro nível é

Observe que só precisamos de um segundo nível se o primeiro nível precisar de


mais de um bloco de armazenamento, e que só precisamos de um terceiro nível se o
segundo nível precisar de mais de um bloco de armazenamento. Podemos repetir a
abordagem anterior até que cada entrada de cada nível do indicador caiba em um único
bloco. Esse bloco nesse nível é conhecido como o nível “índice de topo”. Cada nível reduz
o número de entradas do nível anterior, dividindo-o pelo índice de entrada fan-out para
podermos usar a fórmula 1 ≤ (r1/((fo)t)) para calcular t. Como resultado, um índice com
vários níveis e entradas no primeiro nível terá aproximadamente t níveis.

O esquema multinível descrito aqui pode ser aplicado a qualquer tipo de índice,
seja primário, de agrupamento ou secundário, desde que o índice no nível superior tenha
valores distintos para K(i) e entradas de tamanho fixo. A melhoria no número de blocos
acessados quando um índice multinível é usado para pesquisar um registro é visto no
Exemplo 3.
Um indicador primário de dois níveis que é semelhante à organização ISAM
(Indexed Sequential Access Method — Método de Acesso Sequencial Indexado).

Observe que também podemos ter um índice multinível primário que pode ser
expandido. De acordo com um índice denso, isso pode ser determinado acessando o
primeiro nível do índice (sem ter que acessar um bloco de dados), pois há uma entrada de
índice para cada registro no arquivo.
Uma estrutura organizacional comum para documentos usados em aplicativos de
processamento de dados comerciais é um arquivo organizado usando um índice primário
multinível baseado em seu campo de classificação. Esse tipo de organização é conhecido
como “arquivo sequencial ordenado” sendo usado em um número significativo dos
primeiros sistemas IBM. A inclusão é tratada por algum tipo de estrutura de overflow que
é adicionada periodicamente ao arquivo de dados. O índice é criado durante a
reorganização. A organização ISAM da IBM inclui um índice de dois níveis que se
assemelha muito à estrutura organizacional do disco. O primeiro nível é um índice de
cilindro, que inclui um ponteiro para o índice de trilha do cilindro e o valor da chave de
um registro de âncora para cada cilindro no pacote de discos. O valor chave do índice da
trilha é representado por um registro âncora para cada trilha do cilindro e um ponteiro
para cada trilha. Assim, uma trilha pode ser buscada sequencialmente pelo registro ou
bloco desejado.

Índices Multiníveis Dinâmicos que Usam Árvores-b (B -Trees) e Árvores-b+ (B+ -


Trees)
Esses casos únicos das conhecidas estruturas de dados de árvore são chamados de
Árvores-B e Árvores-B+. Damos uma breve introdução à terminologia usada ao discutir
estruturas de dados em árvore. Cada nó em uma árvore tem um nó pai e vários (zero ou
mais) nós filhos, com exceção de um único nó chamado raiz. O nó raiz não tem pai.
Quem não tem filhos é chamado de nó folha; quem não é folha chama-se nó interno.
Uma subárvore de um nó consiste daquele nó e de todos os seus nós descendentes. Nessa
figura, A é o nó raiz, e seus nós filhos são B, C e D. Os nós E, J, C, G, H e K são nós
folhas.

Estrutura de dados que mostra uma árvore não-balanceada.


Normalmente, mostramos uma árvore com o nó raiz no topo, conforme mostrado
na Figura. Uma maneira de implementar uma árvore é ter tantos ponteiros em cada nó
quantos os nós filhos do nó. Em alguns casos, cada substantivo também possui um
ponteiro para o pai armazenado dentro dele. Além dos ponteiros, a geralmente não contém
nenhum tipo de informação armazenada. Essas informações incluem os valores do campo
de indexação do arquivo, usados para direcionar as buscas por registros específicos
quando um índice individual é implementado usando uma estrutura de árvore.

Árvores de Busca e Árvores-B

Árvores de busca.
Uma árvore de busca é um pouco diferente de um índice multinível. Uma árvore
de busca de ordem p é uma árvore na qual cada nó tem pelo menos p - 1 valores de busca
e p ponteiros na ordem <P1, K1, P2,K2,..., Pq-1, Kq-1, Pq>, onde q ≤ p ; cada Pi é um ponteiro
para um nó filho (ou um ponteiro nulo); e cada Ki é um valor de busca para alguma
coleção ordenada de valores. Todos os valores de pesquisa são considerados exclusivos.
Em uma árvore de busca, dois requisitos sempre devem ser atendidos:
1. Dentro de cada nó, K1 < K2 < ... < Kp-1.
2. Para todos os valores X na subárvore apontada por Pi, temos Ki-1< X < Ki para
1 < i < q; X < Ki para i = 1; e Ki-1 < X para i = q.

Sempre que buscamos um valor X, seguimos o ponteiro Pi apropriado conforme


as fórmulas da segunda condição apresentada acima.

Um nó em uma árvore de busca com ponteiros para as subárvores abaixo dela.


Uma árvore de busca de ordem p = 3.
Podemos empregar uma árvore de pesquisa como um mecanismo para pesquisar
registros mantidos em um arquivo baseado em disco. Os valores na árvore podem vir de
um dos campos do documento chamado campo de busca (que é o mesmo campo de
indexação se um índice multidimensional for usado para guiar a pesquisa). Cada valor de
fruta na árvore está conectado a um ponteiro para o registro de dados que possui esse
valor específico. Uma alternativa seria o ponteiro apontar para o bloco de disco que
contém aquele registro específico. Cada nó da árvore de busca pode ser atribuído a um
determinado bloco de disco, armazenando a própria árvore de pesquisa em um disco.
Quando um novo registro é adicionado, devemos atualizar a árvore de busca inserindo
uma nova entrada com o valor do campo de pesquisa do novo registro e um link para o
novo registro.
Algoritmos são necessários para incluir e excluir intervalos de valores de pesquisa
da árvore de busca, mantendo as duas condições mencionadas anteriormente. Em geral,
esses algoritmos não garantem que a árvore de busca seja balanceada, o que significa
que todos os seus ramos estão no mesmo nível. Manter uma árvore de busca balanceada
é crucial, pois garante que nenhum dos nós estará em níveis extremamente altos,
necessitando de vários pontos de acesso aos blocos ao longo de uma busca na árvore. A
manutenção de uma árvore balanceada fornece uma velocidade de pesquisa uniforme,
independentemente do valor da chave de pesquisa. Outro problema com as árvores de
busca é que a exclusão de registros pode deixar alguns de nós na árvore praticamente
vazios, reduzindo o espaço de armazenamento e aumentando o número de níveis. Ambos
os problemas são abordados pelas árvores B, que especificam condições adicionais
àquelas das árvores de busca.

Árvores-B.
As árvores-B possuem requisitos adicionais que garantem que uma árvore esteja sempre
em estado de equilíbrio e que qualquer espaço excluído nunca se torne excessivo. Para
lidar com essas condições, no entanto, os algoritmos de inclusão e exclusão tornam-se
mais sofisticados. No entanto, a maioria das operações que envolvem inclusões e
exclusões são diretas; eles só se complicam em circunstâncias inusitadas, ou seja, sempre
que tentamos incluir alguém que já está totalmente formado ou excluir alguém para ficar
com menos da metade de seu potencial restante. Mais especificamente, uma árvore B de
ordem p pode ser definida da seguinte forma quando utilizada como estrutura de acesso
em um campo de busca de registros em um banco de dados:
1.Cada nó interno da árvore-B é: <P1, <K1, Pr1>, P2, <K2, Pr2>, . . . , <Kq-1, Prq-1>,
Pq>.
onde q ≤ p. Cada Pi é um ponteiro de árvore conectando um nó da árvore B a
outro. Cada Pri é um link de dados que se conecta a um registro cujo valor do campo de
pesquisa é igual a Ki.

Estruturas de árvore-B. (a) Um nó em uma árvore-B com q – 1 valor de busca. (b)


Uma árvore-B de ordem p = 3. Os valores foram incluídos na sequência 8, 5, 1, 7, 3, 12,
9, 6.
2. Dentro de cada nodo,K1 < K2 < … < Kq-1.
3. Cada nó possui no máximo p ponteiros de árvore.
4. Cada nó, exceto a raiz e os nós folhas, possui pelo menos (p/2)l ponteiros de
árvore. O nó raiz possui pelo menos dois ponteiros de árvore, exceto se ele for o único nó
da árvore.
5. Um nó com q ponteiros de árvore, q ≤ p, possui q – 1 valores de campo de chave
de busca (e, por isso, tem q – 1 ponteiro de dados).
6. Todos os nós folhas estão no mesmo nível. Os nós folhas possuem a mesma
estrutura dos nós internos, exceto que todos os seus ponteiros de árvore Pisão nulos.
Uma árvore-B começa no nível 0 com um único nó (também conhecido como nó
raiz). Assim que o laço estiver completo com um valor p - 1 para a chave de busca e
tentarmos incluir uma nova entrada na árvore, o laço se divide em dois laços no nível 1.
Apenas o valor do meio é mantido no nó raiz, e os valores restantes são divididos
igualmente entre os outros dois nós. Quando um nó, que não é o nó raiz, está completo e
uma nova entrada é feita, o nó é dividido em dois nós no mesmo nível, e a entrada do
meio é transferida para o nó pai com dois ponteiros para os novos nós originários da
divisão. Se o nó-pai estiver sobrecarregado, ele também se separará. Se a raiz for dividida,
a divisão pode ir até a raiz nula, criando um novo nível.
Um nó é combinado com seus vizinhos e isso pode se espalhar até a raiz se a
exclusão de um valor fizer com que um nó seja preenchido com menos da metade de sua
capacidade. Como resultado, a exclusão pode reduzir o número de níveis na árvore. Foi
demonstrado por meio de análises e simulações que, após inúmeras inclusões e exclusões
assimétricas em uma árvore do tipo B, retemos cerca de 69% de todo o nosso potencial
quando o número de valores na árvore se estabiliza. Isso também ocorre com árvores B+.
Se for esse o caso, apenas dividiremos e combinaremos ocasionalmente, tornando a
inclusão e a exclusão bastante eficazes. A árvore crescerá sem problemas se o número de
valores aumentar, mesmo que alguns de nós possam se dividir e demorar mais no geral.
O Exemplo 4 mostra como calcular a ordem p de uma árvore B armazenada em
um disco.
Exemplo 4: Suponha que o campo de busca seja de tamanho V = 9 bytes, o
tamanho do bloco de disco seja B = 512 bytes, um ponteiro de registro (de dados) seja Pr
= 7 bytes, e um ponteiro de bloco seja P = 6 bytes. Cada nó da árvore-B possui no máximo
p ponteiros de árvore, p – 1 ponteiros de dados e p – 1 valores de campo-chave de busca,
que devem caber em um único bloco de disco se cada nó da árvore-B corresponder a um
bloco de disco. Por isso devemos ter:
(p * P) + ((p – 1) * (Pr + V)) ≤ B
(p * 6) + ((p – 1) * (7 + 9)) ≤ 512
(22 * p) ≤ 528
Podemos escolher p de forma que seja um valor grande, tal que satisfaça a
desigualdade acima, que dá p = 23 (p = 24 não é escolhido devido às razões expostas a
seguir).
Em geral, um nó de uma árvore-B pode conter informações adicionais necessárias
para os algoritmos que manipulam a árvore, como o número de entradas no nó e um
ponteiro para o nó pai do nó. Como resultado, antes de fazer o cálculo de p descrito acima,
devemos reduzir o tamanho do bloco pela quantidade de espaço necessária para
armazenar todos esses detalhes. Vamos agora demonstrar como calcular o número de
blocos e níveis para uma árvore do tipo B.
Exemplo 5: Suponha que o campo de busca do Exemplo 4 não seja o campo-
chave de classificação e que construímos uma árvore-B neste campo. Suponha que cada
nó da árvore-B está com 69% de sua capacidade preenchida. Cada nó, em média, terá p *
0,69 = 23 * 0,69, ou aproximadamente 16 ponteiros e, por isso, 15 valores de campo-
chave de busca. O fan-out médio fo = 16. Podemos começar pela raiz e verificar quantos
valores e ponteiros podem existir, em média, em cada nível subsequente:
Calculamos o número médio de entradas em cada nível multiplicando o número
total de ponteiros do nível anterior por 15. Como resultado, uma árvore A-B com dois
níveis tem, em média, 3.840 + 240 + 15 = 4.095 entradas para o bloco tamanho, tamanho
do ponto e tamanho do campo de pesquisa; uma árvore A-B com três níveis tem, em
média, 65.535 entradas.
As árvores do tipo B às vezes são usadas como unidades organizacionais primárias
para documentos. Neste caso, todos os registros são mantidos na árvore-B, e não apenas
as entradas marcadas com a frase de pesquisa e o número de registro. Isso funciona bem
para documentos com um número pequeno de registros relativamente poucos e um
tamanho de registro pequeno. Ao contrário, o fan-out e o número de níveis aumentam
significativamente para permitir um acesso efetivo.
Como um todo, as árvores B fornecem uma estrutura de acesso multinível, que é
uma estrutura de árvore balanceada na qual cada árvore tem pelo menos metade de sua
capacidade. Cada nó em uma árvore-B de ordem p pode ter no máximo p - 1 valores de
busca.

Árvores-B+
A maioria das implementações de indicadores multinível usa uma versão adaptada da
estrutura de dados árvore-B chamada árvore-B+. Cada valor do campo de pesquisa em
uma árvore do tipo B aparece uma vez em um ponteiro de dados em algum nível da árvore.
A estrutura das folhas de uma árvore-nós B+ difere da estrutura de nós internamente
porque os ponteiros de dados são armazenados apenas lá. Se o campo de pesquisa for um
campo de caixa de seleção, nossos arquivos incluirão uma entrada para cada valor,
juntamente com um ponteiro de dados para o registro (ou para o bloco que contém o
registro).Em um campo de pesquisa que não seja um campo de palavra-chave, o ponteiro
aponta para um bloco que contém ponteiros para os registros do arquivo de dados,
adicionando um nível indireto.
As nó folhas de uma árvore B+ são normalmente conectadas umas às outras para
fornecer acesso organizado aos registros do campo de pesquisa. Esses nós se assemelham
ao primeiro nível (base) de um indicador. Os nós internos de uma árvore B+
correspondem aos outros níveis de um indicador multinível. Para direcionar a busca,
vários valores do nosso campo interno de busca árvore-B+ são repetidos. A estrutura dos
nós internos de uma árvore-B+ de ordem p é a seguinte:
1. Cada nó interno é da forma:
<P1, K1, P2, K2, . . . , Pq-1, Kq-1, Pq>
onde q ≤ p e cada Pi é um ponteiro de árvore.
2. Dentro de cada nó interno K1 < K2< … <Kq-1.
3. Para todos os valores X do campo de busca na subárvore apontada por Pi, temos:
Ki–1 < X ≤ Ki para 1 < i < q; X ≤ Ki para i = 1; e Ki–1 < X para i = q.
Os nós de uma árvore-B+. (a) Nó interno de uma árvore-B+ com q – 1 valores de
busca. (b) Nó folha de uma árvore-B+ com q – 1 valores de busca e q – 1 ponteiros de
dados.
4. Cada nó interno possui no máximo p ponteiros de árvore.
5. Cada nó interno possui pelo menos ponteiros de árvore. O nó raiz possui pelo
menos dois ponteiros de árvore se ele é um nó interno.

6. Um nó interno com q ponteiros, q ≤ p, possui q – 1 valores de campo de busca.


A estrutura dos nós folhas de uma árvore-B+ de ordem p é a seguinte:
1. Cada nó folha é da forma:
<<K1, Pr1>, <K2, Pr2>, . . . , <Kq-1, Prq-1>, Ppróximo>
onde q ≤ p, cada Pri é um ponteiro de dados, e Ppróximo aponta para o
próximo nó folha da árvore-B+.
2. Dentro de cada nó folha, K1 < K2 < ... < Kq-1, q ≤ p.
3. Cada Pri é um ponteiro de dados que aponta para o registro cujo valor de
campo de busca é Ki, ou para um bloco do arquivo que contenha o registro (ou para um
bloco de ponteiros de registro que apontam para os registros cujo valor de campo de busca
é Ki se o campo de busca não é uma chave).
4. Cada nó folha possui pelo menos valores.

5. Todos os nós folhas estão no mesmo nível.


Os ponteiros dos nós internos são ponteiros de árvore para os blocos que são nós
de árvore, enquanto os ponteiros dos nós folhas são ponteiros de dados para os registros
ou blocos do arquivo de dados — exceto o ponteiro Ppróximo, que é um ponteiro de árvore
para o próximo nó folha. Iniciando no nó folha mais à esquerda, é possível percorrer os
nós folhas tal como em uma lista encadeada, usando os ponteiros Ppróximo. Isso
proporciona um acesso ordenado aos registros de dados no campo de indexação. Um
ponteiro Panterior também pode ser incluído. Para uma árvore-B+ de um campo que não
seja campo-chave, um nível adicional, indireto, similar ao mostrado na Figura 14.5, é
necessário, assim, os ponteiros Pr são ponteiros de bloco para os blocos que contenham
um conjunto de ponteiros de registros para os verdadeiros registros do arquivo de dados.
Como as entradas dos nós internos de uma árvore-B+ incluem valores de busca e
ponteiros de árvore e não têm ponteiros de dados, mais entradas podem ser agrupadas em
um nó interno de uma árvore-B+ que em uma árvore-B similar. Assim, para o mesmo
tamanho de bloco (nó), a ordem p será maior na árvore-B+ que na árvore-B, conforme
ilustra o Exemplo 6. Isso pode levar a menos níveis na árvore-B+, melhorando o tempo
de busca. Como as estruturas dos nós internos e dos nós folhas de uma árvore-B+ são
diferentes, a ordem p pode ser diferente. Usaremos p para denotar a ordem dos nós
internos e pfolha para denotar a ordem dos nós folhas, que definimos como sendo o número
máximo de ponteiros de dados em um nó folha.
Exemplo 6: Para calcular a ordem p de uma árvore-B+, suponha que o campo-
chave de busca tenha o tamanho V = 9 bytes, que o tamanho do bloco seja B = 512 bytes,
que um ponteiro de registro tenha o tamanho Pr = 7 bytes, e que um ponteiro de bloco
tenha o tamanho P = 6 bytes, tal como no Exemplo 4. Um nó interno da árvore-B+ pode
ter até p ponteiros de árvore e p – 1 valores de campo de busca; estes devem caber em um
único bloco. Por isso, temos:
(p * P) + ((p – 1) * V) ≤ B
(p * 6) + ((p – 1) * 9) ≤ 512
(15 * p) ≤ 521
Podemos escolher p de modo que seja o maior valor que satisfaça a desigualdade
acima, o que resulta em p = 34. Ele excede o valor 23 da árvore-B, resultando em um fan-
out maior e mais entradas em cada nó interno de uma árvore-B+ do que na árvore-B
correspondente. O número de valores e ponteiros em nossas folhas de árvore B+ seria o
mesmo, com exceção de que um dos ponteiros seria um ponteiro de dados. Como
resultado, a ordem de grandeza dos pontões pode ser calculada da seguinte forma:
( pfolha * (Pr + V)) + P ≤ B
(pfolha*(7 + 9)) + 6 ≤ 512
(16 * pfolha) ≤ 506
Segue que cada nó folha pode ter até pfolha = 31 combinações de valores de
chave/ponteiros de dados, supondo que os ponteiros de dados são ponteiros de registro.
Da mesma forma, para árvore-B, podemos precisar de informações extras para
implementar os algoritmos de inclusão e exclusão para cada nó. Esses detalhes podem
incluir a natureza do laço (interno ou folha), o número de entradas ativas e ponteiros para
os pais e irmãos do laço. Com isso, antes de realizar os cálculos acima para p e pfolha ,
devemos reduzir o tamanho do bloco pelo espaço necessário para todos esses detalhes. O
próximo exemplo mostra como podemos calcular o número de entradas em uma árvore
B+.
Exemplo 7: Suponha que construímos uma árvore-B+ para o campo do Exemplo
6. Para calcular o número aproximado de entradas da árvore-B+, supomos que cada nó
da árvore-B+ está com 69% de sua capacidade preenchida. Em média, cada nó interno
terá 34 * 0,69 ou aproximadamente 23 ponteiros, e por isso, 22 valores. Cada nó folha,
em média, terá 0,69 * polha = 0,69 * 31 ou aproximadamente 21 ponteiros de registros de
dados. Uma árvore-B+ terá os seguintes números médios de entradas em cada nível:

Para os tamanhos de bloco, de ponteiro e de campo de busca dados acima, uma


árvore-B+ de três níveis mantém, em média, até 255.507 ponteiros de registro. Compare
esse resultado com as 65.535 entradas da árvore-B correspondentes do Exemplo 5.
Um exemplo de inclusão em uma árvore-B+ de ordem p = 3 e pfolha = 2.
A próxima figura ilustra uma exclusão em uma árvore-B+. Sempre que uma
entrada é excluída, ela é retirada da lista de entradas. Ela também deve ser retirada de lá
caso a entrada ocorra em um laço interno. Em última instância, o valor à esquerda do nó
folha deve ocupar seu lugar no nó interno, pois esse valor agora está mais à direita na sub-
árvore. O underflow pode resultar de exclusão se o número de entradas no nó-folha for
reduzido abaixo do número mínimo exigido. Neste caso, tentamos encontrar um nó folha
irmão (aquele que está imediatamente à esquerda ou à direita do nó e tem underflow) e
redistribuir as entradas entre o nó e seu irmão de forma que ambos sejam preenchidos
pelo menos até a metade de seus capacidade; caso contrário, o nó é mesclado com seu(s)
irmão(s), reduzindo o número de nó folhas. Uma estratégia comum é tentar redistribuir
as entradas com o irmão à esquerda; se isso não for possível, faz-se uma tentativa de
redistribuição com o irmão à direita. Se isso também não for possível, os três nós são
unidos (fundidos) em dois de nós. Nesse caso, o underflow pode se espalhar internamente,
pois é necessário um toco de árvore e um valor de busca menor. Isso poderia se espalhar
e diminuir os três níveis.
Um exemplo de exclusão em uma árvore-B+.
Variações de árvores-B e árvores-B+. Em algumas circunstâncias, a condição 5
da árvore-B (ou árvore-B+) que exige que cada indivíduo tenha pelo menos metade de
sua capacidade preenchida pode ser alterada para exigir que cada indivíduo tenha pelo
menos dois terços de sua capacidade preenchida. Cada nó é pré-enchimentado quando a
construção do índice começa, até aproximadamente os fatores de pré-enchimento
especificados. Recentemente, pesquisadores propuseram afrouxar a exigência de que uma
pessoa tenha pelo menos metade de sua capacidade preenchida e, em vez disso, permitir
que uma pessoa fique completamente vaga antes de uma fusão, a fim de simplificar o
algoritmo de exclusão. Estudos em simulação mostram que isso não requer uma
quantidade significativa de espaço adicional sob inclusões e exclusões distribuídas
aleatoriamente.

Índices em Chaves Múltiplas


Até aqui, assumimos que as senhas primárias ou secundárias utilizadas para acessar os
documentos possuem atributos únicos (campos). Muitos pedidos de recuperação e
atualização envolvem muitos atributos. Se uma determinada combinação de atributos for
utilizada com frequência, será vantajoso definir uma estrutura de acesso que forneça um
acesso eficiente por meio do uso de uma chave-valor, que é uma combinação desses
atributos.
Por exemplo, considere o arquivo EMPREGADO que possui os atributos NRD
(número do departamento), IDADE, RUA, CODIGOPOSTAL, SALÁRIO e CODIGO
HABILIDADE, bem como a chave SSN (número do CPF). Pense na solicitação: "Liste
os funcionários do Departamento 4 cuja idade é 59". Esteja ciente de que nem NRD nem
IDADE são atributos chave, o que significa que qualquer valor de pesquisa para qualquer
um deles retornaria vários registros. As seguintes estratégias de busca de alternativas
podem ser consideradas:
1. Supondo que NRD tenha um índice, mas IDADE não, acesse os registros que
têm NRD = 4 utilizando o índice, e então selecione entre eles aqueles registros que
satisfaçam IDADE = 59.
2. De maneira alternativa, se IDADE é indexada, mas NRD não é, acesse os
registros que têm IDADE = 59 utilizando o índice, e então selecione entre eles aqueles
registros que satisfaçam NRD = 4.
3. Se os índices foram criados para ambos NRD e IDADE, ambos os índices
podem ser usados; cada índice fornece um conjunto de registros ou um conjunto de
ponteiros (para blocos ou registros). A interseção desses conjuntos de registros ou de
ponteiros resulta naqueles registros que satisfazem ambas as condições, na localização
daqueles registros que satisfazem ambas as condições ou nos blocos nos quais os registros
satisfazem ambas as condições.
No final do dia, todas essas opções produzem o resultado certo. No entanto, se
uma coleção de registros que satisfaçam individualmente qualquer uma das condições
(NRD = 4 e IDADE = 59) for grande, mesmo que apenas alguns registros satisfaçam a
condição combinada, nenhuma das técnicas listadas acima será particularmente eficaz
para a solicitação de pesquisa mencionada. . Existem várias formas de abordar a
combinação "NRD, IDADE" ou "IDADE, NRD" como ferramenta de busca
multiatributo. Vamos passar brevemente por essas técnicas abaixo. Estou me referindo a
árvores compostas, ou árvores com vários atributos.

Índices Ordenados em Atributos Múltiplos


Uma classificação lexicográfica desses valores de tupla estabelece uma hierarquia na
consulta de pesquisa. Em nosso exemplo, todas as portas departamentais que levam ao
departamento número três vêm antes que levam ao departamento quatro. Assim, "3, n"
vem antes de "4, m" para qualquer valor de m. A classificação ascendente das chaves para
NRD = 4 seria 4, 18, 4, 19, 4, e assim por diante. A classificação lexicográfica funciona
de maneira semelhante à classificação em cadeia de caracteres. Qualquer índice composto
por n atributos funciona de maneira semelhante a qualquer índice mostrado até agora
neste capítulo.

Hashing Particionado
O hash particionado é uma extensão do hashing estático externo que permite o acesso
com base em várias senhas. Ele é apropriado apenas para comparações de igual valor;
consultas de intervalo não são tratadas. Ao participar do hashing, a função hash tem como
objetivo produzir um resultado para uma chave que contém n componentes.
Com endereços hash distintos. O endereço do bucket é uma combinação desses n
endereços. Portanto, é possível encontrar uma frase de pesquisa solicitada procurando nos
intervalos apropriados que correspondem às partes do endereço em que estamos
interessados.
Considere uma chave de busca composta, por exemplo. Obteríamos um endereço
de bucket de 8 bits se NRD e IDADE fossem submetidos a codificações de hash de 3 e 5
bits, respectivamente. Assuma que NRD = 4 tem o endereço hash "100" e que IDADE =
59 tem o endereço hash "10101". Portanto, para buscar por NRD = 4 e IDADE = 59,
deve-se ir ao endereço do bucket 100 10101; da mesma forma, para buscar todos os
funcionários com IDADE = 59, devem ser pesquisados todos os buckets (oito deles) cujos
endereços sejam "000 10101", "001 10101" etc. Um benefício de participar do hashing é
que é simples estendê-lo a qualquer um dos atributos. O endereço do buckets pode ser
projetado de forma que os bits estejam na ordem correta.

Arquivos Grid
Outra opção é configurar o arquivo EMPREGADO como uma grade de documentos. Se
quiséssemos acessar um segundo conjunto de duas chaves, digamos NRD e IDADE,
como no nosso exemplo, poderíamos construir uma matriz com escala (ou dimensão)
linear para cada um dos atributos de busca. A Figura abaixo mostra uma matriz para o
objeto EMPREGADO com uma escala linear para NRD e outra para IDADE. Para obter
uma distribuição uniforme desse atributo, as escalas são definidas. Desta forma, nosso
exemplo demonstra que a escala linear NRD possui NRD = 1, 2 combinado como valor
0 da escala, enquanto NRD = 5 corresponde ao valor 2 da escala.
Como resultado, nossa requisição para NRD = 4 e IDADE = 59 mapeia a célula
(1,5) de acordo com a matriz. O bucket relevante incluirá os registros para essa
combinação. Essa metodologia é particularmente útil para consultas de intervalo que
convertem uma coleção de células em um conjunto de valores ao longo de escalas
lineares. A ideia de uma grade arquitetônica pode, teoricamente, ser aplicada a qualquer
número de critérios de pesquisa. Os critérios de pesquisa estabelecem que a matriz deve
ter várias dimensões. Como resultado, a matriz possibilita a divisão do objeto nas
dimensões dos atributos de busca e fornece acesso por meio da combinação de valores
nessas dimensões. Os documentos em grade têm um bom desempenho em termos de
aceleração do acesso à informação.

Uso de Hashing e outras Estruturas de Dados como Índices


Também é possível criar estruturas de controle de acesso baseadas em hash e comparáveis
a índices. As entradas do índice "K, Pr" (ou "K, P") podem ser organizadas como uma
tabela hash extensível dinamicamente; a busca por uma entrada usa o algoritmo de busca
baseado em hash para K. Uma vez que uma entrada é descoberta, o ponteiro Pr (ou P) é
usado para localizar o registro correspondente no arquivo de dados. Outras estruturas de
busca também podem ser empregadas como índices.

Comparação entre Índices Lógicos e Físicos


Anteriormente, acreditávamos que cada entrada para um índice (ou) sempre continha um
ponteiro físico (Pr) que especificava o endereço físico do arquivo no disco usando um
número de bloco e um deslocamento. Isso às vezes é chamado de "endereço físico" e
tem a desvantagem de que o ponteiro mudará se o arquivo for movido para outro local no
disco. Por exemplo, suponha que uma estrutura organizacional primária para um artigo
seja baseada em hashing linear ou extensível. Nesse caso, sempre que um balde é
dividido, alguns registros são atribuídos a novos
Podemos usar uma estrutura chamada "índice lógico" para melhorar essa
situação. As entradas deste índice têm a forma "K, Kp". Cada entrada tem um valor K
para o campo de indexação secundário que corresponde ao valor Kp para o campo
organizador primário do documento. Usando a organização primária do documento, um
programa pode localizar o valor correspondente de Kp e usá-lo para acessar o registro
procurando por K no índice secundário. Como resultado, a adição dos indicadores lógicos
introduz um nível indireto entre os dados e a infraestrutura de acesso. Eles são usados
quando se prevê que os endereços físicos dos registradores mudarão com frequência. O
custo desse nível de indireção é uma busca adicional.

Referências:
Algoritmos e Estruturas de Dados II - ORGANIZAÇÃO DE ARQUIVOS INDEXADOS
- Prof. Osvaldo Alves dos Santos
ACH2024 Índices (Acesso indexado) - Prof. Helton Hideraldo Bíscaro
Banco de Dados I Estruturas de Índices para Arquivos - Jhonata R.de Peder Marcelo
Schuck
Estruturas de Indexação de Dados Tipos de Índices- Profa. Dra. Cristina Dutra de Aguiar
Ciferri
Sistemas de Banco de Dados - 4ª edição - Elmasri, Ramez; Navathe, Shamkant B.

Você também pode gostar