Estruturas de Informação Árvores B _______________________________________________________________________________________________ ÁRVORES B Até agora manipulamos a estrutura árvore binária em memória principal. Também queremos no entanto guardar árvores em disco e carregar a informação do disco para a memória principal, quando precisamos dela. Como sabemos, a informação em memória principal é temporária enquanto no disco é permanente, e a memória principal disponível é muito menor do que espaço em disco, daí a vantagem de podermos ter gravada a informação em memória secundária. Contudo, o acesso ao disco é muito mais lento do que o acesso à memória principal e se usássemos a informação em disco, estruturada numa árvore binária de pesquisa, dado que temos tantos nós quantas as chaves o número de operações de I/O seria elevado e portanto o tempo exigido, por exemplo, numa pesquisa seria longo. Para evitar este inconveniente usaremos grandes blocos de informação agrupando vários objectos, cada um incluindo uma chave, num só nodo. A escolha da ordem da árvore (do número de elementos por nó) depende do tamanho do bloco de disco, isto é cada nó deverá ter o tamanho dum bloco de disco para que cada vez que fazemos um acesso a um nó para uma operação de pesquisa ou actualização só precisemos de realizar uma única transferência do disco. Usaremos então uma árvore n-ária de determinado tipo designada por árvore B. Neste tipo de árvores para cada valor M, a que se chama ordem da árvore, cada nodo contem no máximo 2*M elementos e no mínimo M, excepto o nó raiz, que pode transgredir esta última regra, admite-se que o nó raiz tenha no mínimo 1 elemento . Em aplicações reais usamos para M valores da ordem da centena, usar-se-ão sempre chaves únicas, não existem portanto chaves repetidas. Nos exemplos que vamos usar para explicar os algoritmos de manipulação deste tipo de árvores consideraremos cada elemento reduzido a uma chave. Tal como acontece com as árvores binárias de pesquisa usaremos as chaves para procurar um elemento na árvore de uma maneira eficiente. A estrutura de cada nodo de uma árvore B, será dada por um array de chaves (k[i] elementos ) de comprimento 2*M, um array de apontadores de comprimento 2*M+1 (ptr[i]) _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 1 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ e um campo inteiro, cnt, que indica quantos dos 2*M campos de informação estão realmente ocupados. Por outras palavras diremos que, cada nodo tem no máximo 2*M+1 filhos , uma vez que possui 2*M+1 apontadores e no mínimo M. O número de elementos guardados em cada nodo é menor do que o número de apontadores. Utilizando a notação simplificada de n, ki e pi em vez de rz->cnt, rz->k[i] e rz->ptr[i] em que rz é o apontador para o nodo, teremos as seguintes condições a verificar nos nodos da árvore: 1 <= n <= 2*M para o nó raiz M<= n <= 2*M para os outros nodos. Os elementos em cada nodo estão ordenados por chave, k0 < k1 < k2<...<kn-1 n p0 k0 p1 k1 p2 k2 p3 ... ... ... ... kn-1 pn Esquematicamente representa-se abaixo uma árvore B de ordem 2. _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 2 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Se o nodo é uma folha o valor de cada apontador pi é nulo. Se o nodo não é folha, cada um dos n+1 ponteiros aponta para outro nodo, filho do nodo dado. Se i>0 todas as chaves do nó apontado por pi, são maiores do que Ki-1. Se i<n todas as chaves do nó apontado por pi são menores do que ki. Resultante do modo de construção ,as árvores B apresentam outra caracterìstica, são equilibradas e mantêm os nós folha todos ao mesmo nível, logo o caminho do nó raiz a qualquer folha tem exactamente o mesmo comprimento. Esta forma interessante das árvores B tem no entanto o seu preço, há muitas vezes espaço de memória que está reservado e não está a ser utilizado. É sem dúvida uma estrutura elegante e eficiente para muitas aplicações embora os algoritmos de manipulação não sejam imediatos. As árvores B foram inventadas por Bayer e McCreight. Do exposto deduzimos que para o mesmo número de elementos armazenado numa árvore binária e numa árvore B, a altura da árvore B é muito menor que a da binária. Para uma árvore B de ordem M, com n elementos e todos os nós completamente preenchido, a altura será da ordem de O (log 2M+1 n). Vejamos seguidamente a forma de lhe juntar elementos. _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 3 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Suponhamos que a nossa árvore B estava vazia e vamos juntar-lhe elementos cujas chaves são os valores: 60, 20, 80, 10. Obteremos então um nó raiz que contem a seguinte informação: n=4 p0= p1= p2= p3= p4= Nulo k0= 10 k1= 20 k2= 60 k3=80 O juntar de um elemento será sempre feito num nó folha A configuração da árvore será então a seguinte: Se agora juntássemos o valor 15, fazendo uma inserção ordenada obteríamos a seguinte sequência: 10,15,20,60,80 Mas como se ultrapassou a capacidade do nó, há que dividir o nó em 2, cada um com M elementos, neste nosso exemplo cada nó teria dois elementos, e o valor do meio será inserido no nível superior (neste exemplo, o valor 15), ficando a árvore com a seguinte configuração: _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 4 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Juntando os valores30 e 70 obteríamos: Juntemos agora o valor 22, então a folha mais à direita terá que dividir-se, obtendo-se a seguinte +arvore: _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 5 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Se fossem adicionados os valores: 12, 18, 19, 4, 5, 6, 2, 3 a árvore apresentaria o seguinte aspecto A adição do elemento com valor 1 ,vai obrigar a nova configuração da árvore, uma vez que o nó mais à esquerda será obrigado a dividir-se o elemento do nó tentará ser colocado no nó pai mas como este já está totalmente preenchido será também obrigada a dividir-se subindo o valor do meio para um novo nó raiz, ficando a árvore com a seguinte representação _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 6 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Uma vez que os valores por nó estão ordenados, a pesquisa em cada nó é feita através do algoritmo de pesquisa binária, conforme se exemplifica no algoritmo abaixo. Pesquisa Binária nos nós da árvore B Os valores de retorno da pesquisa binária serão: 0 se x <= a[0] n se x > a[n-1] r se a[r-1] < x <= a[r] Algoritmo pesquisa_binaria(x, a, n) // a é o array de chaves, x o valor a procurar, n o numero de // elementos no nó Inicio Se x <= a[0] Então devolve 0 Fse Se x > a[n-1] Então devolve n Fse esq = 0 dir = n-1 Enquanto (dir-esq) > 1 i = (dir+esq)/2 Se x <= a[i] Entao dir = i Senão esq = i Fse Fenquanto devolve dir Fim Descrevemos agora o algoritmo de pesquisa de um valor x, na árvore B de raiz t. Algoritmo pesquisar(x,t) Inicio Enquanto t!= Nulo k=t->chave n=t->cnt i=pesquisa_binária(x, k, n) _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 7 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Se (i<n e x==k[i]) Então encontrou(t,i) sai Fse t=t->ptr[i] Fenquanto escreve "Item" x "não encontrado" Fim Algoritmo encontrou (t, i) Inicio escreve "Encontrado na posição " i escreve "do nó com os seguintes conteúdos : " Para i=0 até i < t->cnt escreve t->chave[i] i++ Fpara Fim Quanto ao algoritmo de inserção de elementos numa árvore B, atendendo ao que já foi dito, recorreremos a um algoritmo recursivo, ins, que descerá na árvore desde a raiz até à folha onde será junto o elemento x, e no caminho de regresso da recursividade será colocado o elemento que sobe quando se dá a divisão de nós, designado por xnovo, e será ligado o novo nó, designado por tnovo. O valor de retorno deste algoritmo recursivo será 0, 1 ou 2 indicando ao pai respectivamente se a operação ainda não está completa (caso de divisão de nó no nível abaixo), se a operação está terminada ou se houve duplicação de chave. A invocação do método de inserir poderá ser feito do seguinte modo: raiz =inserir( x, raiz) Algoritmo: inserir(x,t) Inicio codigo=ins(x, t, xnovo, tnovo) // xnovo e tnovo são passados por referência Se (codigo==2) Então "Chave duplicada" Se (codigo==1) _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 8 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Então devolve t Senão u=obternodo() u->cnt=1 u->chave[0]=xnovo u->ptr[0]=t u->ptr[1]=tnovo devolve u Fim codigo==0 Se o novo elemento ainda não foi junto à árvore codigo==1 Se o novo elemento já se juntou à árvore codigo==2 Se o elemento já existe na árvore Algoritmo recursivo ins(x, t, y, u) // y e u por referencia Inicio //Consideramos: p=t->ptr n=t->cnt k=t->chave //Verificar se t é um apontador numa folha Se (t==Nulo) Então u=Nulo y=x devolve 0 //Seleccionar o apontador p[i] e tentar inserir x na subárvore em que p[i] é raiz i=pesquisa_binária(x, k, n) // array de chaves do nó Se (i<n e x==k[i]) Então devolve 2 // chave duplicada Fse codigo=ins(x, p[i], xnovo, tnovo) Se (codigo) Então devolve codigo _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 9 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Fse //A inserção na subárvore não foi completamente sucedida //Vamos tentar inserir xnovo e tnovo no nó corrente Se (n<MM) Então i=pesquisa_binária(xnovo, k, n) Para j=n até j>i k[j]=k[j-1] p[j+1]=p[j] j -Fpara k[i]=xnovo p[i+1]=tnovo n++ devolve 1 Fse /*O nó corrente está cheio, por isso há que dividir. Passar o item k[M], no meio da sequência aumentada para trás, através do parâmetro y, para que possa subir na árvore. Também passa um apontador para o nó criado de novo para trás, através de u. Devolve 0 para indicar que a inserção não está completa. */ Se (i==MM) Então k_final=xnovo p_final=tnovo Senão k-final=k[MM-1] p_final=p[MM] Para j=MM-1 até j>i k[j]=k[j-1] p[j+1]=p[j] j -Fpara k[i]=xnovo p[i+1]=tnovo _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 10 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ Fse y=k[M] n=M u=obternodo() u->cnt=M Para j=0 até j<M-1 u->k[j]=k[j+M+1] u->ptr[j]=p[j+M+1] j ++ Fpara u->ptr[M-1]=p[MM] u->k[M-1]=k_final u->ptr[M]=p_final devolve 0 FIM Quanto à operação de eliminação de um elemento, proceder-se-á de forma inversa da de inserção. Assim, o primeiro passo consiste na pesquisa do elemento a eliminar. Se o nó onde se encontra o elemento é um nó folha então há que retirar o elemento do array. Se o resultado dessa eliminação é um nó com menos de M elementos, é realizada uma reorganização como se segue: Se existe dentro da mesma subárvore, um irmão direito com mais de M elementos, à folha em questão pode ser junto um elemento emprestado da folha irmã, envolvendo o nó pai, isto é, o elemento mais à esquerda da irmã direita sobe para o pai e o do pai desce para a folha onde foi efectuada a eliminação. Alternativamente, caso a folha irmã não possa emprestar nenhum elemento porque só tem M elementos, haverá que juntar as duas folhas. Neste caso, também um elemento do pai descerá para a folha nova, ficando esta com um máximo de 2*M elementos. Se por acaso o pai ficar com menos de M elementos esta mesma reorganização terá que se repetir nos níveis superiores. No caso do elemento a eliminar não pertencer a uma folha, mas a um nó interno, então a posição do elemento que vai sair é substituído pelo valor do elemento imediatamente _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 11 Departamento de Engª Informática Estruturas de Informação Árvores B _______________________________________________________________________________________________ anterior em ordem simétrica, isto é , do filho esquerdo o mais à direita, até atingir uma folha( método idêntico ao usado nas árvores binárias de pesquisa). Se por acaso a folha contem M elementos será então necessário proceder à reorganização da árvore como explicado no caso da eliminação de um elemento pertencente a um nó folha. _______________________________________________________________________________ Instituto Superior de Engenharia do Porto 12 Departamento de Engª Informática