UNISINOS - UNIVERSIDADE DO VALE DO RIO DOS SINOS CENTRO DE CIÊNCIAS EXATAS E TECNOLÓGICAS 65098 - ESTRUTURAS AVANÇADAS DE DADOS PROFESSORA: ANA PAULA LÜDTKE FERREIRA ALUNOS: DATA: 06/06/2001 BRAULIO DE OLIVEIRA IGOR DREWS FABIO COSTA FABIANO ARAUJO GUILHERME LAZZARI OTAVIO GASPARETO RED-BLACK TREES AVL TREES B-TREES QUAD-TREES www.trees.cjb.net [email protected] UNISINOS ÍNDICE INTRODUÇÃO..................................................................................................... 3 RED-BLACK TREES ........................................................................................... 4 AVL TREES ....................................................................................................... 10 QUAD-TREES ................................................................................................... 15 B-TREES ........................................................................................................... 21 IMPLEMENTAÇÕES ......................................................................................... 28 CONCLUSÃO .................................................................................................... 39 BIBLIOGRAFIA .................................................................................................. 40 ESTRUTURAS AVANÇADAS DE DADOS 2 UNISINOS INTRODUÇÃO Antes de iniciarmos esta introdução, gostaríamos de lembrar que nosso site está sempre em atualização. Pretendemos desenvolver com mais calma e clareza novos algoritmos para as árvores apresentadas no trabalho. Caso prefira, entre em contato conosco por e-mail. Tentaremos ajudá-lo da melhor maneira possível. Neste trabalho demonstraremos quatro diferentes estruturas de árvores com pesquisa O(log n). Red-Black, AVL, B-Tree e Quad-Tree serão as estruturas demonstradas. Daremos uma explicação sobre cada uma, demonstrando seus graus de complexidade, análise detalhada de vários casos possíveis de ocorrem durante suas execuções e algoritmos genéricos representativos das estruturas. Apresentaremos também, no final do trabalho, o código fonte, desenvolvido após o entendimento e conclusões chegadas após o estudo. Tentamos, pois achamos muito válido, colocar não só os conceitos demonstrados no seminário, como tudo o que aprendemos durante o mesmo, seja com os alunos de outros grupos, com a professora, ou até mesmo com entrevistas a outras entidades. ESTRUTURAS AVANÇADAS DE DADOS 3 UNISINOS RED-BLACK TREES Como sabemos, ao estudarmos Árvores Binárias de Pesquisa de altura h temos um conjunto básico de operações dinâmicas que possuem um tempo de execução O(h). Entre elas estão a Inserção, Deleção, Pesquisa, e outras. O problema é que funcionam bem para árvores com pouca altura. As Red-Black Trees é uma estrutura que mantém a árvore aproximadamente balanceada, garantindo assim, que essas operações tenham sempre um tempo O(log n), no pior caso. Red-Black Trees é uma árvore binária com um pequena diferença. Possui um bit extra de armazenamento da cor do nodo, que pode ser Vermelho (Red) ou Preto (Black). A cada inserção, o algorítimo testa um conjunto de propriedades. Caso essas ESTRUTURAS AVANÇADAS DE DADOS 4 UNISINOS propriedades não sejam satisfeitas, rotações e/ou ajustes de cores ocorrem mantendo assim a àrvore “balanceada”. A àrvore estará balanceada se atender as seguintes propriedades: 1. Todo nodo é vermelho ou preto; 2. Toda folha (NIL) é preta; 3. Se um nodo é vermelho, então ambos seus filhos são pretos; 4. Todo caminho simples de um nodo para uma folha descendente contém o mesmo número de nodos pretos. Exemplo de uma árvore Red-Black. (Fig. 1). Podemos notar que as quatro propriedades estão sendo satisfeitas. Fig. 1 Chamamos de black-height de um nodo, o número de nodos pretos do nodo x até uma folha, não incluindo esse nodo x. Denotamos a black-height por bh(x). Definese a altura de uma Red-Black como a black-height de sua raiz. ESTRUTURAS AVANÇADAS DE DADOS 5 UNISINOS Rotações Ao inserirmos ou deletarmos em uma árvore Red-Black temos uma violação de suas propriedades. Para manter a árvore O(log n) e “balanceada”, realizamos rotações, que podem ser para a esquerda ou para direita. Em alguns casos realizaremos rotações duplas para a esquerda ou duplas para a direita. Tanto a Left-Rotate como a Right-Rotate executam em O(1). Ocorrem apenas mudanças com ponteiros, sendo que os outros campos do nodo permanecem inalterados. Veremos adiante que as mesmas rotações serão usadas nas árvores AVL. Veja no exemplo o resultado das rotações. (Fig. 2) Fig. 2 Pseudo-código do Left-Rotate, assumindo que right[x] <> NIL. Inserção Para que haja um melhor entendimento da Inserção nesse tipo de árvore, vamos estudar um caso passo a passo. 1º) Vamos pegar como o exemplo a árvore da Fig. 1. 2º) Agora iremos inserir o nodo com o valor 4, que chamaremos de x. Podemos ver que temos dois nodos vermelhos seguidos e, o tio y de x é vermelho. Temos o caso 1. ESTRUTURAS AVANÇADAS DE DADOS 6 UNISINOS 3º) Trocamos as cores dos nodos 4, 7 e 8. 4º) Movemos x para seu avô, 7. O pai de x, o nodo 2, ainda é vermelho. Marcamos o tio de x como y, que é preto. Neste caso temos o caso 2. 5º) Movemos x para cima e aplicamos uma rotação para a esquerda. ESTRUTURAS AVANÇADAS DE DADOS 7 UNISINOS 6º) Mesmo o tio de x é sendo preto, o pai de x impede que tenhamos uma Red-Black. 7º) Alteramos as cores dos nodos 7 e 11 e rotacionamos para a direita. ESTRUTURAS AVANÇADAS DE DADOS 8 UNISINOS 8º) Agora sim temos a nova Red-Black Tree, “balanceada”, em O(log n). Deleção O caso da deleção é levemente mais complicado, mas funciona basicamente como a inserção, precisando sempre fazer os ajustes baseados nas propriedades da RedBlack, garantindo assim que ela fique balanceada. Não falaremos sobre deleção neste trabalho. Pretendemos assim estudarmos mais a fundo a questão das vantagens de se ter uma árvore balanceada. ESTRUTURAS AVANÇADAS DE DADOS 9 UNISINOS AVL TREES O nome AVL vem de seus inventores Adelson-Velskii and Landis. Foi a primeira proposta de balanceamento de árvores binárias de pesquisa. É aproximadamente balanceada. Suas sub-árvores diferença de altura de no máximo 1 nível, garantindo assim a execução em O(log n). Vale lembrar que as árvores AVL são muito parecidas com as Red-Black, tanto que as rotações, por exemplo, que veremos logo em seguida são as mesmas. Uma árvore AVL é uma árvore de pesquisa binária com as seguintes propriedades: 1. A sub-árvore de todo nodo difere em altura por no máximo 1 nível; 2. Toda sub-árvore é uma AVL Tree. ESTRUTURAS AVANÇADAS DE DADOS 10 UNISINOS Temos aqui um esquema de como deve ser a árvore AVL para que ela seja considerada balanceada. Podemos notar nos exemplos abaixo dois casos de árvores binárias. A primeria é uma AVL, já a segunda não. Vamos a uma melhor análise: 1º) Note que cada sub-árvore esquerda tem altura 1 maior que cada sub-árvore direita, portanto é uma AVL. 2º) A sub-árvore com raiz 8 tem altura 4 e sub-árvore com raiz 18 com altura 2. ESTRUTURAS AVANÇADAS DE DADOS 11 UNISINOS Procedimentos básicos A Inserção é um pouco complicada pois envolve vários casos, como as RedBlacks. Cada nodo, ao invés de ter um bit para a cor, terá um campo extra controlando seu fator de balanceamento. Este fator indica se a árvore está left-heavy ( a altura da subárvore esquerda é 1 maior que a sub-árvore direita), balanceada (ambas sub-árvores tem a mesma altura) ou right-heavy (a altura da sub-árvore direita é 1 maior que a sub-árvore esquerda). Para que mantenhamos a árvore “balanceada”, garantindo execução em tempo O(log n), precisamos realizar rotações, que são as mesmas das árvores Red-Black, como já dito anteriormente. Elas são necessárias pois a cada inserção há uma verificação da altura dos nodos da árvore. Na figura acima, vemos que um novo nodo foi adicionado à esquerda do nodo 1, fazendo com que a altura de 2 maior que que a direita (verde). Uma rotação à direita é executada e então a árvore torna-se balanceada. Seja um nó qualquer da árvore, apontado por n: se FatBal(n) = 0, as duas subárvores têm a mesma altura; se FatBal(n) = -1, a subárvore esquerda é mais alta que a direita em 1; se FatBal(n) = +1, a subárvore direita é mais alta que a esquerda em 1. A vantagem de uma árvore AVL sobre uma degenerada (desbalanceada) está na maior eficiência nas suas operações de busca, pois, sendo a altura da AVL bem menor, o ESTRUTURAS AVANÇADAS DE DADOS 12 UNISINOS número necessário de comparações diminui sensivelmente. Por exemplo, numa árvore degenerada de 10.000 nós, são necessárias, em média, 5.000 comparações, numa busca; numa árvore AVL, com o mesmo número de nós, essa média baixa para 14. Inserção Sabemos que a inserção sempre se dá numa folha, podendo assim alterar os fatores de balanceamento e desequilibrar a árvore. O algoritmo que apresentamos fará a inserção seguida do ajuste dos fatores de balanceamento e, se houver desequilíbrio (algum FatBal(n) diferente de -1, 0 ou +1), corrigirá a estrutura fazendo rotação de nós. Rotação Como a árvore está desbalanceada à esquerda, há uma rotação simples para direita, demonstrada abaixo. No próximo caso há um desajuste à direita, ocorrendo assim uma rotação simples para a esquerda. Dois outros tipos de rotações são usados para ajustes de balanceamento. Rotação dupla para a esquerda e dupla para a direita. O primeiro é composta de uma simples à esquerda seguida de uma simples à direita. A rotação dupla para a direita é o inverso. ESTRUTURAS AVANÇADAS DE DADOS 13 UNISINOS Nunca é demais lembrar, como muitas vezes já falado, que essas rotações ocorrem nas árvores Red-Black também. A diferença é que as Red-Black se baseiam na cor do nodo como fator de balanceamento, já ás AVLs analisam a altura de cada sub-árvore de cada nodo. ESTRUTURAS AVANÇADAS DE DADOS 14 UNISINOS QUAD-TREES Uma Quad-Tree é uma estrutura de dados utilizada para codificar imagens. A idéia fundamental atrás da Quad-Tree é que qualquer imagem pode ser dividida em quatro quadrantes. Sendo que cada quadrante pode ser dividido novamente em quatro subquadrantes e assim sucessivamente. Na Quad-Tree, a imagem é representada por um nodo pai, enquanto que os quatro quadrantes são representados por quatro nodos filho, em uma ordem prédeterminada. Sendo que a Quad-Tree é um tipo especial de árvore onde todos os nodos ou são nodos folha ou têm quatro nodos filho. Tendo como utilização principal o armazenamento de uma decomposição recursiva do espaço. O algoritmo verifica se a imagem possui apenas uma única cor, em caso positivo é criado apenas um nodo com a informação da respectiva cor. Caso contrário a imagem é dividida em 4 quadrantes, sendo que inicialmente a quadtree gerada deverá ser um nodo pai com quatro nodos filho (representando os quatro quadrantes), agora é feita uma nova verificação em cada quadrante para ver se cada um possui uma única cor em seu respectivo quadrante, se for constatado isso, o respectivo ESTRUTURAS AVANÇADAS DE DADOS 15 UNISINOS nodo filho recebe a informação da cor (se tornando um nodo cor), caso contrário, esse quadrante será subdividido em 4 subquadrantes e uma nova verificação será feita em cada um deles sucessivamente, até que o quadrante da imagem possua apenas uma única cor. Sendo que a cada nova subdivisão o nodo atual se torna pai de quatro novos nodos e quando a subdivisão cessa a informação da cor é inserida no nodo (se tornando um nodo cor). Ou em outras palavras, os nodos folha correspondem às regiões onde não há mais necessidade de subdivisão contendo a informação da cor resultante. (Fig. 1) Imagem QuadTree Resultante Figura 1: Os nodos na cor cinza são os nodos onde ocorreu a dividsão da imagem em quatro quadrantes. São portanto nodos-pai. Criação da árvore O algoritmo de criação recebe dois parâmetros: a raiz da árvore e uma imagem (lida como String). Em seguida, este algoritmo verifica se a imagem possui somente uma cor. Se possuir, então esta cor é colocada no campo COR do único nodo criado até este momento, que também foi recebido como parâmetro por este algoritmo (Raiz). Se não possuir uma cor somente, então, a imagem passada como parâmetro é dividida em quatro quadrantes iguais e cria-se quatro novos nodos, que serão filhos do nodo raiz recebido como parâmetro. Estes quatro quatros quadrantes normalmente são identificados por NO (Noroeste), NE (Nordeste), SO (Sudoeste) e SE (Sudeste). Em materiais em inglês eles serão identificados por NW, NE, SW ou SE, por motivos óbvios. ESTRUTURAS AVANÇADAS DE DADOS 16 UNISINOS Assim, mesma função é aplicada, recursivamente, em cada um dos quatros quadrantes. A recursão irá terminar quando se chegar um quadrante que possuam somente uma cor, para daí armazenar as informações de cor em nodo Folha. É interessante ressaltar que uma Quad-Tree possuirá 2 estruturas de Nodo: Nodo Interno: É nodo que possuirá 4 ponteiros para suas subdvisões. Este tipo de nodo não irá armazenar nenhuma informação de imagem propriamente, assim não encontraremos nele informações como cor ou tamanho. Nodo Folha: É o nodo que irá se encarregar de armazenar informações da cor da Imagem. Não necessariamente, qualquer Quad-Tree possuirá estes dois tipos de nodo. Por exemplo, se uma imagem passada como parâmetro for sólida de uma cor só, teremos somente o nodo raiz, que na realidade será um nodo folha, pois armazenará diretamente as informações de cor. Inserção A inserção de nodos em uma QuadTree se dará em dois momentos: Na criação de uma QuadTree: Este processo foi exaustivamente explicado no item anterior. Nele, vemos que a partir da leitura da Imagem, iremos incluir Nodos Folha ou Nodos Internos, dependendo das cores do quadrante em questão. Na Alteração da Imagem a partir da qual a QuadTree foi gerada: No momento em que temos uma Quad-Tree gerada a partir de uma imagem, e alteramos a imagem, podemos optar por criar novamente toda a Quad-Tree ou simplesmente incluir novos nodos que reflitam as alterações realizadas. A Inserção se dará a partir de uma pesquisa recursiva na QuadTree, até chegarmos ao Quadrante em questão, representado por um Nodo Folha. No momento em que encontramos tal nodo, devemos retirar a informação de cor que o mesmo possuía, criar novos nodos, quantos forem necessários (recursivamente), que serão descendentes do nodo inicial. Nos novos nodos serão armazenadas as informações de cor que reflitam ESTRUTURAS AVANÇADAS DE DADOS 17 UNISINOS as alterações realizadas na imagem. O exemplo abaixo ilustra o conceito de inclusão abordado. Deleção A exclusão de Nodos poderá ser realizada quando um determinado nodo interno tem todos os seus descendentes armazenando a mesma cor. Quando isto ocorre, podese excluir todos os nodos descendentes e armazenar a cor no ancestral correspondente. Imagem ESTRUTURAS AVANÇADAS DE DADOS QuadTree Resultante 18 UNISINOS Figura 3: Imagem inicial com sua respectiva quadtree, depois o 2º quadrante da imagem é todo pintado de vermelho, tornando os quatro nodos filhos de seu nodo pai com a cor vermelha. E para não haver desperdício de quatro nodos para uma mesma cor, esses são excluídos e o nodo pai passe a ser nodo folha com a informação da cor vermelha. Um caso onde houve alteração das cores dos nodos e para não haver desperdício houve também exclusão dos mesmos. Pesquisa Tem-se várias formas de pesquisa em QuadTrees. Abordaremos a que consideramos mais importante: a Pesquisa por Cor. A pesquisa por cor varre toda a QuadTree gerada, tentado localizar uma cor específica. A aplicação prática para este tipo de pesquisa seria a realização de operações de troca de cores em imagens. Por exemplo, em uma determinada imagem deseja-se que tudo que aparece em Verde seja mostrado em Azul. Este tipo de aplicação, tem uso prático na medicina, onde em determinadas radiografias, células defeituosas são mostradas em cinza, que seria a sua cor natural, porém para visualizá-las melhor pode-se trocar a sua cor para preto. Caminhamentos Temos algumas formas de caminhamentos em Quad-Trees, que podem ser: ESTRUTURAS AVANÇADAS DE DADOS 19 UNISINOS Aplicações 3D Studio MAX 3 (um dos mais populares softwares para animação e renderização de imagens 3D): É utilizado aqui a quadtree para se fazer o controle da profundidade de cor para sombras RayTrace. Corrigir deformaçôes de cores nas fotos. Como por exemplo, quando se tira uma foto e a pessoa está com os olhos avermelhados, podendo isso ser corrigido utilizando a quadtree. Compactação de imagens. Vantagens e desvantagens de seu uso Uma grande vantagem é que sua estrutura é bastante robusta e compacta, mas existem alguns fatores que a tornam, em outros casos, inviáveis. Se a imagem tem grandes áreas com uma única cor (como por exemplo: o preto ou branco) haverá um grande ganho em espaço e uma árvore bastante compacta, mas se a imagem tenha grandes áreas com diferentes cores (como por exemplo: azul, amarelo, verde, etc...) a árvore resultante será muito maior não proporcionando um ganho considerável em espaço. Um considerável consumo de CPU quando se trabalha com imagens complexas (diversas cores, formas irregulares como curvas e triângulos, etc) na geração da árvore. Não há balanceamento, podendo ocorrer de nodos muito extensos de um lado e de outro muito curto, deixando a busca um pouco lenta. ESTRUTURAS AVANÇADAS DE DADOS 20 UNISINOS B-TREES São árvores de pesquisa balanceada feitas para trabalhar bem em discos magnéticos ou outros dispositivos de armazenamento secundários com acesso direto. BTrees são similares às Red-Blacks, mas elas são melhores pois minimizam operações de entrada e saída no disco. B-Trees podem ter muitos filhos, diferenciando-se assim das Red-Blacks. Assim, o fator de ramificação de desse tipo de árvore pode ser muito grande, sendo determinado pelas características da unidade de disco. São parecidas com as Red-Blacks também, pois todos nodo n de uma B-Tree tem altura O(log n), mas conseguem ser menor que as Red-Blacks, porque o fator de ramificação pode ser muito grande. Sendo assim, B-Trees podem ser usadas na implementação de um conjunto de operações dinâmicas em tempo O(log n). B-Trees generalizam árvores de pesquisa binária de uma melhor maneira. Se um nodo x de uma B-Tree contém n[x] elementos, então x tem n[x]+1 filhos. Os elementos em um nodo x são usados como pontos de divisão separando a área dos elementos manuseados por x em n[x] + 1 sub-áreas, cada manuseio por um filho de x. Quando ESTRUTURAS AVANÇADAS DE DADOS 21 UNISINOS procuramos por um elemento em uma B-Tree, nós fazemos em (n[x] + 1) modos de decisão baseados em comparações com n[x] elementos armazenados no nodo x. M DH BC FG Q T X JKL NP RS VW YZ Veja na figura acima uma B-Tree onde os elementos são consoantes de inglês. Um nodo interno x contendo n[x] elementos tem n[x] + 1 filhos. Todos com a mesma altura na árvore. Note que a altura da uma B-Tree cresce logaritmicamente com o número de nodos que ela contém. Acompanhe abaixo a definição mais detalhada de uma B-Tree. Obs.: Adotaremos key = elemento. leaf = folha root = raiz Um B-Tree é uma árvore enraizada, com raiz root[T] tendo as seguintes propriedades: 1. Todo nodo x tem os seguintes campos: a) n[x], o número de elementos atualmente armazenados em um nodo b) os mesmos n[x] elementos, armazenados em ordem crescente: x. key1[x] <= key2[x] <= ...<= keyn[x], e c) a folha[x], um valor booleano que é True or False, representando se x é uma folha ou nodo interno, respectivamente. 2. Se x é um nodo interno, ele contém n[x] + 1 ponteiros c1[x], c2[x],...cn[x]+1 para seus filhos. Nodos folha não tem filhos, assim os campos ci são indefinidos ESTRUTURAS AVANÇADAS DE DADOS 22 UNISINOS 3. Os elementos keyi[x] separa os tamanho dos elementos armazenados em cada sub-árvore. Se ki é um elemento armazenado em uma sub-árvore com raiz ci[x], então: K1 <= key1[x] <= k2 <= key2[x],...<=key[n]x[x] <= kn[x]+1 4. Toda folha tem a mesma profundidade, que é a altura h da árvore. 5. Há limites abaixo e acima do número de elementos que um nodo pode conter. Estes limites podem ser expressos em termos de um inteiro fixo t >= 2 chamado de grau mínimo de uma B-Tree. a) Todo outro nodo além da raiz tem no mínimo t – 1 elementos. Todo outro nodo interno como a raiz tem no mínimo t filhos. Se a árvore não é vazia, a raiz precisa ter no mínimo um elemento. b) Todo nodo pode contém no máximo 2t – 1 elementos. Um nodo interno pode ter no máximo 2t filhos. Nós sabemos que um nodo é cheio se ele contém exatamente 2t – 1 elementos. A B-Tree mais simples ocorre quando t = 2. Todo nodo interno então tem entre 2, 3 ou 4 filhos, e nós temos árvore 2, 3, 4. Na prática, de qualquer modo, valores muito grandes de t são tipicamentes usados. Altura de uma B-Tree O número de acessos a disco requeridos pela maioria das operações em uma BTree é proporcional à altura da B-Tree. Vamos analizar em seguida, a altura no pior caso da árvore. Teorema: Se n >= 1, então para todo elemento n-key T de uma B-Tree de altura h e grau mínimo t >= 2, h <= logt n+1 / 2 ESTRUTURAS AVANÇADAS DE DADOS 23 UNISINOS 1 t-1 t-1 t t t t-1 t-1 ........... ........... t-1 ........... 0 1 1 2 t-1 2 2t .......... 3 2t2 Prova: Se uma B-Tree tem algura h, seu número de nodos é minimizado quando a raiz contém um elemento e todos outros nodos contém t – 1 elementos. Neste caso, há 2 nodos com profundidade 1, 2t nodos e profundidade 2, 2t2 nodos e profundidade 3, e assim por diante, até a profundidade h ter 2th-1 nodos. Na figura acima podemos ver claramente uma árvore com altura h = 3. O número n de elementos satisfaz a inequação. n >= 1 + (t - 1) 2ti-1 = 1 + 2(t - 1)( th – 1 / t-1) = 2th – 1 demonstrando o Teorema. Podemos notar a força de uma B-Tree se comparada com uma Red-Black. A altura da B-Tree cresce O(log n) em ambos os casos(vale lembrar que t é uma constante). Nas B-Trees a base logarítmica pode ser muito grande. Essas árvores salvam um fator de aproximadamente log t acima das Red-Black no número de nodos examinados para mais de três operações. Como, ao examinarmos um determinado nodo em uma árvore requer um acesso à disco, o número de acessos à disco é reduzido substancialmente com BTrees. Adotaremos agora, para demonstrar as operações básicas em uma B-Tree que: A raiz de uma B-Tree está sempre na memória principal, assim, a leitura de disco na raiz é sempre requerida. Uma escrita no disco da raiz é requerido, quando a raiz é mudada. ESTRUTURAS AVANÇADAS DE DADOS 24 UNISINOS Nenhum nodo que passados por parâmetros precisa já ter tido uma leitura de disco. Criando uma B-Tree Vazia Usaremos uma procedure B-Tree Create e após uma B-Tree Insert que adicionará novos nodos. As duas usam, para alocar uma página no disco um Allocate-Node, que executa em O(1). A operação básica na inserção de um elemento em uma B-Tree é o Splitting, que irá dividir um nodo inteiro com 2t – 1 elementos baseados em sua mediana em dois outros tendo t – 1 elementos cada. O tempo gasto pela CPU é de O(t). Inserção de um elemento Resumindo vamos ter o seguinte: Um elemento só é inserido na folha e se o nodo não estiver cheio. Se o nodo estiver cheio, o elemento central vai o nodo acima ordenadamente e o elemento à direita vai para um novo nodo à direita, reorganizando os ponteiros. Uma explicação mais detalhada agora. Uma inserção de um elemento k em uma B-Tree de altura h leva O(h) acessos de disco. O tempo requerido pela CPU é O(th) = O(t logt n). Garantimos através do B-Tree Split Child que a recursão nunca descenderá para um nodo cheio. Usaremos uma outro procedura auxiliar B-Tree Insert NonFull que garante que o nodo não está cheio, retornando verdadeiro neste caso. Para um melhor entendimento, vamos acompanhar o exemplo abaixo de uma inserção de um elemento. ESTRUTURAS AVANÇADAS DE DADOS 25 UNISINOS O grau mínimo t para esta B-Tree é 3, então um nodo pode ter no máximo 5 valores. Os nodos serão modificados durante a inserção. a) Árvore inicial para este exemplo. GMPX ACDE JK NO RSTUV YZ b) Inserindo B na árvore. É uma inserção normal em um nodo folha. GMPX ABCDE JK NO RSTUV YZ c) Agora temos a inserção do valor Q. O nodo RSTUV é dividido em dois nodos contendo RS e UV, o T é movido para a raiz e Q é inserido no ramo mais esquerdo(nodo RS). P GM ABCDE TX JKL NO QRS UV YZ d) Agora podemos acompanhar a inserção do valor L na árvore. A raiz é dividida pelo lado direito, desde que esteja cheia, e o grau da B-Tree em altura é aumentado em uma unidade. O L é inserido em uma folha contendo JK. P CGM AB DEF TX JKL NO QRS UV YZ e) O resultado da inserção de F na árvore. O nodo ABCDE é dividido antes de F e este é inserido mais à direita dos dois valores (nodo DE). ESTRUTURAS AVANÇADAS DE DADOS 26 UNISINOS Deleção A deleção em uma B-Tree é similar ao procedimento de inserção, mas um pouco mais complicado. Há uma série de casos que podem ocorrer durante a deleção, mas que não vão ser estudados neste trabalho. Para conhecimento, a complicada procedure de deleção de uma B-Tree leva somente O(h) operações de disco, desde que somente O(1) chamadas a leitura e escrita no disco são feitas entre invocações recursivas desta procedure. O tempo requerido pela CPU é O(th) = O(t logt n). ESTRUTURAS AVANÇADAS DE DADOS 27 UNISINOS IMPLEMENTAÇÕES Abaixo seguem as implementações das árvores apresentadas no trabalho. Algumas das árvores implementadas contém apenas a estrutura, cabendo ao programador definir a melhor forma de implementação. RED-BLACK struct t_red_black_node { enum { red, black } colour; void *item; struct t_red_black_node *left, *right, *parent; left_rotate( Tree T, node x ) { node y; y = x->right; /* Turn y's left sub-tree into x's right sub-tree */ x->right = y->left; if ( y->left != NULL ) y->left->parent = x; /* y's new parent was x's parent */ ESTRUTURAS AVANÇADAS DE DADOS 28 UNISINOS y->parent = x->parent; /* Set the parent to point to y instead of x */ /* First see whether we're at the root */ if ( x->parent == NULL ) T->root = y; else if ( x == (x->parent)->left ) /* x was on the left of its parent */ x->parent->left = y; else /* x must have been on the right */ x->parent->right = y; /* Finally, put x on y's left */ y->left = x; x->parent = y; } rb_insert( Tree T, node x ) { /* Insert in the tree in the usual way */ tree_insert( T, x ); /* Now restore the red-black property */ x->colour = red; while ( (x != T->root) && (x->parent->colour == red) ) { if ( x->parent == x->parent->parent->left ) { /* If x's parent is a left, y is x's right 'uncle' */ y = x->parent->parent->right; if ( y->colour == red ) { /* case 1 - change the colours */ x->parent->colour = black; y->colour = black; x->parent->parent->colour = red; /* Move x up the tree */ x = x->parent->parent; } else { /* y is a black node */ if ( x == x->parent->right ) { /* and x is to the right */ /* case 2 - move x up and rotate */ x = x->parent; left_rotate( T, x ); } /* case 3 */ x->parent->colour = black; x->parent->parent->colour = red; right_rotate( T, x->parent->parent ); } } else { /* repeat the "if" part with right and left exchanged */ } ESTRUTURAS AVANÇADAS DE DADOS 29 UNISINOS /* Colour the root black */ T->root->colour = black; } ESTRUTURAS AVANÇADAS DE DADOS 30 UNISINOS AVL #include <string.h> #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <process.h> #include <dos.h> #define true 0 #define false 1 /* definicao da estrutura */ typedef struct nodo { int info; int FatBal; struct nodo *esquerda; struct nodo *direita; } T_nodo; T_nodo *PtrRaiz, *PtrTrb, *PtrAnt, *PtrA, *PtrB, *PtrAAnt, *PtrNovo; int PosChave; T_nodo *cria_nodo(int, T_nodo *, T_nodo *, T_nodo *); int BuscaAvl(int); void insereAvl(int); void AjustaFatoresAvl(int); void BalanceiaAvl(int); void RotacaoSimples(); void RotacaoDupla(); /* funcao principal do programa */ void main() { int numero=0, achou; clrscr(); PtrRaiz=NULL; do { gotoxy(5,5); printf("Entre com a informacao : "); scanf("%i",&numero); if (numero != -1) { achou=BuscaAvl(numero); if (achou == 1) { insereAvl(numero); ESTRUTURAS AVANÇADAS DE DADOS 31 UNISINOS AjustaFatoresAvl(numero); BalanceiaAvl(numero); } else { gotoxy(25,22); printf("Tentativa de insercao de chave j existente"); delay(500); } } } while (numero != -1); } /* funcao que procura se uma chave j foi inserida */ int BuscaAvl(int numero) { PtrAAnt = NULL; PtrAnt = NULL; PtrTrb = PtrRaiz; PtrA = PtrRaiz; while (numero != PtrTrb->info && PtrTrb != NULL) { if (PtrTrb->FatBal != 0) { PtrA = PtrTrb; PtrAAnt = PtrAnt; } PtrAnt = PtrTrb; if (numero < PtrTrb->info) PtrTrb = PtrTrb->esquerda; else PtrTrb = PtrTrb->direita; } if (PtrTrb == NULL) return(1); else return(0); } /* funcao de alocacao de uma nova estrutura do tipo nodo */ T_nodo *cria_nodo(int numero) { T_nodo *novo; novo=(T_nodo *) malloc(sizeof(struct nodo)); if (novo == NULL) { gotoxy(25,22); printf("Memoria insuficiente para alocar estrutura"); exit(1); } ESTRUTURAS AVANÇADAS DE DADOS 32 UNISINOS novo->info=numero; novo->FatBal=0; novo->esquerda=NULL; novo->direita=NULL; return(novo); } /* funcao que coloca um elemento na arvore */ void insereAvl(int numero) { PtrNovo = cria_nodo(numero); if (PtrRaiz != NULL) if (numero < PtrAnt->info) PtrAnt->esquerda = PtrNovo; else PtrAnt->direita = PtrNovo; else PtrRaiz=PtrNovo; } void AjustaFatoresAvl(int numero) { if (PtrA != NULL || PtrB != NULL) { if (numero < PtrA->info) { PtrB = PtrA->esquerda; PtrTrb = PtrA->esquerda; } else { PtrB = PtrA->direita; PtrTrb = PtrA->direita; } while (PtrTrb->info != numero) { if (numero < PtrTrb->info) { PtrTrb->FatBal = -1; PtrTrb = PtrTrb->esquerda; } else { PtrTrb->FatBal = 1; PtrTrb = PtrTrb->direita; } } } else if (PtrRaiz->esquerda != NULL || PtrRaiz->direita != NULL) if (PtrRaiz->esquerda == NULL) // se nulo na esquerda ESTRUTURAS AVANÇADAS DE DADOS 33 UNISINOS else PtrRaiz->FatBal = 1; // senao PtrRaiz->FatBal = -1; // insercao feita na direita // insercao feita na esquerda } void BalanceiaAvl(int numero) { if (PtrA != NULL && PtrB != NULL) { if (numero < PtrA->info) PosChave = -1; else PosChave = 1; if (PtrA->FatBal == 0) PtrA->FatBal = PosChave; else if (PtrA->FatBal == PosChave -1) PtrA->FatBal =0; else { if (PtrB->FatBal == PosChave) RotacaoSimples(); else if (PtrB->FatBal == -PosChave) RotacaoDupla(); if ((PtrAAnt != NULL) && (PtrRaiz->esquerda != NULL || PtrRaiz->direita != NULL)) if (PtrAAnt->direita == PtrA) PtrAAnt->direita = PtrTrb; else PtrAAnt->esquerda = PtrTrb; if (PtrB->direita == PtrRaiz || PtrB->esquerda == PtrRaiz) PtrRaiz = PtrB; } } } void RotacaoSimples() { if (PtrA->FatBal == 1) { PtrA->direita = PtrB->esquerda; PtrB->esquerda = PtrA; } else { PtrA->esquerda = PtrB->direita; PtrB->direita = PtrA; } PtrTrb = PtrB; PtrA->FatBal = 0; ESTRUTURAS AVANÇADAS DE DADOS 34 UNISINOS PtrB->FatBal = 0; } void RotacaoDupla() { if (PtrA->FatBal == 1) { PtrTrb = PtrB->esquerda; PtrB->esquerda = PtrTrb->direita; PtrTrb->direita = PtrB; PtrA->direita = PtrTrb->esquerda; PtrTrb->esquerda = PtrA; } else { PtrTrb = PtrB->direita; PtrB->direita = PtrTrb->esquerda; PtrTrb->esquerda = PtrB; PtrA->esquerda = PtrTrb->direita; PtrTrb->direita = PtrA; } if (PtrTrb->FatBal == -PosChave) { PtrA->FatBal = 0; PtrB->FatBal = PosChave; } if (PtrTrb->FatBal == 0) { PtrA->FatBal = 0; PtrB->FatBal = 0; } if (PtrTrb->FatBal == PosChave) { PtrA->FatBal = -PosChave; PtrB->FatBal = 0; } PtrTrb->FatBal = 0; } ESTRUTURAS AVANÇADAS DE DADOS 35 UNISINOS QUAD-TREE search(typedef key, tree t) { int i, indx, noteq; while(t != NULL) { indx = noteq = 0; for (i=0; i<K; i++) { indx = indx << 1; if (key[i] > t->k[i]) indx++; if (key[i] != t->k[i]) noteq++; } if ( noteq ) t = t->p[indx]; else { found( t ); return; } } notfound( key ); }; /**************************************************/ tree insert(typedef key, tree t) { int i, indx, noteq; if (t==NULL) t = NewNode( key ); else { indx = noteq = 0; for (i=0; i<K; i++) { indx = indx << 1; if ( key[i] > t->k[i] ) indx++; if ( key[i] != t->k[i] ) noteq++; } if ( noteq ) t->p[indx] = insert( key, t->p[indx] ); else Error; /*** chave já existe ***/ } return( t ); }; ESTRUTURAS AVANÇADAS DE DADOS 36 UNISINOS B-TREE /*-------------------------------------------------------------------*/ /* Estrutura da árvore /-- ------------------------------------*/ typedef struct btnode { int d; /*** number of active entries ***/ typekey k[2*M]; /*** chave ***/ struct btnode *p[2*M+1]; /*** ponteiros para as sub-arvores ***/ } node, *btree; /*-------------------------------------------------------------------*/ /* Insercao /-----------------------------------------------------*/ typekey InternalInsert(typedef key, btree t) { int i, j; typekey ins; btree tempr; extern btree NewTree; if (t == NULL) { NewTree = NULL; return(key); } else { for ( i=0; i<t->d && key>t->k[i]; i++ ); if ((i<t->d) && (key == t->k[i])) return Error; /*** chave já existente ***/ else { ins = InternalInsert(key, t->p[i]); if (ins != NoKey) /*** the key in "ins" has to be inserted in present node ***/ if (t->d < 2*M) InsInNode( t, ins, NewTree ); else {/*** cria um novo nodo ***/ if ( i<=M ) { tempr = NewNode( t->k[2*M-1], NULL, t->p[2*M] ); t->d--; InsInNode(t, ins, NewTree); } else tempr = NewNode( ins, NULL, NewTree ); /*** move chaves e ponteiros ***/ for (j=M+2; j<=2*M; j++) ESTRUTURAS AVANÇADAS DE DADOS 37 UNISINOS InsInNode(tempr, t->k[j-1], t->p[j]); t->d = M; tempr->p[0] = t->p[M+1]; NewTree = tempr; return( t->k[M] ); } } return( NoKey ); } }; btree insert( typedef key, btree t ) { typekey ins; extern btree NewTree; typekey InternalInsert(); ins = InternalInsert( key, t ); if (ins != NoKey) return(NewNode(ins, t, NewTree)); return(t); }; ESTRUTURAS AVANÇADAS DE DADOS 38 UNISINOS CONCLUSÃO Após a realização deste trabalho, juntamente com o seminário e conceitos aprendidos em aula conseguimos compreender alguns métodos de balanceamento de árvores. Chegamos à conclusão que o uso desses métodos, devidamente escolhido para cada ocasião ou problema a ser desenvolvido, só nos traz vantagens. A principal razão é que teremos sempre acesso em tempo logarítmico, o que é muito bom. Resolvemos com o uso dessas estruturas, por exemplo, o problema de uma inserção ordenada em uma árvore binária, onde teríamos uma enorme ramificação para o lado direito da árvore. O grande problema aqui é o tempo de pesquisa, onde, mantendo balanceada, conseguimos esse mesma pesquisa em tempo logarítmico. ESTRUTURAS AVANÇADAS DE DADOS 39 UNISINOS BIBLIOGRAFIA CORMEN, Thomas; LEISERSON, Charles; RIVEST, Ronald. Introduction to Algorithms. MIT Press, 1996 SHAFFER, Clifford A. A Practical Introduction to Data Structures and Algorithm Analysis. Prentice-Hall, 1996. KNUTH, D. E. The Art of Computer Programming: Sorting and Searching. Addison-Wesley, 1997. Sites na Web: http://www.eli.sdsu.edu/courses/fall95/cs660/notes/RedBlackTree/RedBlack.html http://www.inf.unisinos.br/~anibal http://whatis.techtarget.com/ ESTRUTURAS AVANÇADAS DE DADOS 40 UNISINOS ESTRUTURAS AVANÇADAS DE DADOS 41