Árvores 2 caminhamentos e construção. caminhamentos em árvore binária um caminhamento, ou...

Post on 17-Apr-2015

107 Views

Category:

Documents

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

ÁRVORES 2ÁRVORES 2

Caminhamentos e Construção

Caminhamentos em árvore binária

• Um caminhamento, ou percurso, em uma árvore binária é o ato de percorrer sistematicamente todos os nós da árvore, sendo cada nó visitado exatamente uma vez.

• Um caminhamento completo sobre uma árvore produz um seqüência linear de nós, de maneira que cada nó tem um nó seguinte e um nó anterior.

Caminhamentos em árvore binária

• No caso da árvore binária existem três tipos de caminhamento em profundidade mais frequentemente utilizados:– LRN (pós-ordem)– NLR (pré-ordem)– LNR (in-ordem)

Caminhamento LRN

• O caminhamento LRN, o início é feito pela raiz, depois visitamos o ramo esquerdo de cada nó, em seguida o ramo direito e, finalmente, o próprio nó.

• Esse caminhamento é recursivo já que cada um dos ramos esquerdo e direito pode ser considerado uma subárvore

-

* /

A C +

D E

B

O caminhamento LRN para a árvore abaixo fica assim:

*

A B

Subárvore esquerda

A

B

*

Subárvore esquerda

Subárvore direita

Nó da subárvore

/

C +

D E

Subárvore direita

C Subárvore esquerda

+

D E

Subárvore direita

D

E

+

Subárvore esquerda

Subárvore direita

Nó da subárvore

- Nó da árvore

Caminhamento LRN

• Para a árvore, se considerarmos que os nós folhas são os operandos e os ramos são os operadores, este caminhamento fornece a notação posfixa.

• A B * C D E + / -

Caminhamento NLR

• O caminhamento NLR também inicia pela raiz, primeiro é visitado o próprio nó, em seguida a subárvore esquerda e por último a subárvore direita.

-

* /

A C +

D E

B

O caminhamento NLR para a árvore abaixo fica assim:

*

A B

- Nó da árvore

Subárvore esquerda

*

A

B

Nó da subárvore

Subárvore esquerda

Subárvore direita

/

C +

D E

Subárvore direita

/

C

Nó da subárvore

Subárvore esquerda

+

D E

Subárvore direita

+

D

E

Nó da subárvore

Subárvore esquerda

Subárvore direita

Caminhamento NLR

• Como no caminhamento NLR, os operadores precedem os operandos, ele é conhecido como pre-ordem.

• - * A B / C + D E

Caminhamento LNR

• No caminhamento LNR, começando pela raiz, primeiro é visitada a subárvore esquerda, em seguida o nó e finalmente a subárvore direita.

-

* /

A C +

D E

B

O caminhamento LNR para a árvore abaixo fica assim:

*

A B

Subárvore esquerda

A

B

*

Subárvore esquerda

Nó da subárvore

Subárvore direita

/

C +

D E

- Nó da árvore

Subárvore direita

/

C

+

D E

Subárvore esquerda

Nó da subárvore

Subárvore direita

• O caminhamento LNR é conhecido também como in-ordem pois fornece a notação infixa quando a árvore armazena expressões aritméticas

• A * B - C / D + E

+

D

E

Subárvore esquerda

Nó da subárvore

Subárvore direita

59

22 69

13 61 78

71 83

49

35

26 41 75

Verificar os caminhamentos LRN, NLR e LNR

• Na árvore anterior, para o caminhamento LNR, a seqüência fornecida está em ordem crescente. Uma árvore binária na qual o caminhamento LNR fornece uma lista ordenada é chamada de árvore binária de classificação.

• Uma árvore binária de classificação é uma árvore binária onde todos os nós da subárvore esquerda são menores ou iguais ao valor da raiz.

• Esta propriedade se aplica recursivamente a cada nó da árvore.

• Os caminhamentos que vimos até aqui foram caminhamentos em profundidade. Há uma outra forma de caminhamento, não tão usada que é o caminhamento em largura.

• No caminhamento em largura, visitamos os nós da árvore por níveis da árvore visitando todos os nós de um nível da árvore da esquerda para a direita e então partindo para o próximo nível.

• No exemplo dado anteriomente o percurso em largura ficaria: - * / A B C + D E

Implementação de Árvore Binária

• Tal como dito para as outras árvores, árvores binárias podem ser implementadas usando-se vetor ou alocação dinâmica.

• A implementação com vetores só é conveniente se a árvore for completa.

• Para guardar os nós é feito o percurso em largura com a raiz sendo o primeiro nó do array.

Implementação de Árvore Binária

• Considerando que em Java o primeiro índice do vetor é 0 e não 1, teremos:

• Em Java o pai de um nó i sempre estará na posição (i-1) div 2.

• Para um nó na posição i seus filhos (se existirem) estarão na posição 2i+1 e 2i+2

Implementação de Árvore Binária• Se a árvore não for completa podemos transformá-la em

uma árvore cheia pelo acréscimo de nós fantasmas. Isto deve ser indicado em algum campo do nó. Assim, cada elemento do array consistirá de dois campos: um para conter o valor do nó e o outro para indicar se o nó é nulo ou não. Na figura abaixo, usamos um campo booleano para indicar se o nó é valido (T) ou se é um nó nulo (F).

Implementação de Árvore Binária

• As mesmas expressões apresentadas para uma árvore completa se aplicam também aqui. A diferença é que agora temos de testar, através do campo Ind, se o nó é nulo ou não.

• Em muitas situações, a representação de árvores através de arrays pode não ser tão eficiente. O motivo disso é que gastamos muita memória para armazenar os nós nulos, além de termos que rearrumar o array toda vez que houver uma remoção. Entretanto, se não for permitido remoções na árvore e se o número máximo de nós for conhecido, o uso de arrays pode ser uma solução bastante útil.

Implementação de Árvore BináriaRepresentação Dinâmica

• A representação dinâmica de uma árvore binária é feita com a utilização de variáveis dinâmicas onde cada nó contém, além da informação, dois ponteiros: um para apontar para a subárvore esquerda e o outro para apontar para a subárvore direita como mostrado na figura abaixo.

Implementação de Árvore BináriaRepresentação Dinâmica

• Para facilitar a implementação de alguns algoritmos iremos manter no nó mais um ponteiro que aponta para o seu pai. Além disso, o formato do nó em Java que iremos utilizar nos nossos algoritmos será o seguinte:

boolean temFilhoEsq() { if (filhoEsq == null) return false; else return true; } boolean temFilhoDir() { if (filhoDir == null) return false; else return true; }} //Fim da classe Node

class Node{ String nome; Node filhoEsq; Node filhoDir; Node pai; //Construtor principal, recebe o conteúdo e o pai do nó Node(String n, Node noPai) { nome = n; pai = noPai; filhoDir = filhoEsq = null; } //Construtor para a raiz, na verdade chama o de dois Node(String n) { this(n,null); }

Implementação de Árvore Binária

• A vantagem de se utilizar estruturas dinâmicas para a representação de árvores binárias é a possibilidade delas crescerem ou diminuírem durante a execução do programa.

Inserção de Nós em Uma Árvore Binária

• Para poder inserir um nó em uma árvore binária precisamos antes conhecer quem será seu pai e se esse nó será o filho esquerdo ou o filho direito, como mostrado na seguinte listagem:

public boolean insere(Node noPai, String info, char tipoFilho) { Node aux; boolean Ok = false; if (noPai == null) //Significa que estou inserindo a raiz { aux = new Node(info); raiz = aux; Ok = true; } else { if ((tipoFilho == 'E') && (noPai.temFilhoEsq())) { System.out.println("*** ERRO: Impossível inserir, já possui filho esquerdo!"); Ok = false; return false; } else if ((tipoFilho == 'D') && (noPai.temFilhoDir())) { System.out.println("*** ERRO: Impossível inserir, já possui filho direito! ***"); Ok = false; return false; } aux = new Node(info,noPai); if (tipoFilho == 'E') noPai.insFilhoEsq(aux); else noPai.insFilhoDir(aux); Ok = true; } return Ok; }//Fim do método insere

Navegando Através de Uma Árvore Binária

• Agora que já sabemos como construir uma árvore binária, vamos discutir como navegamos através dela para visitar todos os seus nós. Já vimos que o termo visitar consiste em processar o item de um nó para fazer qualquer tipo de operação, seja simplesmente imprimir seu conteúdo ou fazer uma operação mais complexa. A navegação será feita através de um dos percursos vistos anteriormente, ou seja em pré-ordem, em ordem simétrica, em pós-ordem e em níveis. Esses tipos de percursos podem ser implementados usando recursividade ou sem o uso dela.

• Vejamos a seguir o código para os três tipos de percurso:

Percurso in-ordem

public void inOrdemRec(Node atual) { if (atual != null) { inOrdemRec(atual.getFilhoEsq()); System.out.println(atual.getNome()); inOrdemRec(atual.getFilhoDir()); } }

Percurso Pré-ordem

public void preOrdemRec(Node atual) { if (atual != null) { System.out.println(atual.getNome()); inOrdemRec(atual.getFilhoEsq()); inOrdemRec(atual.getFilhoDir()); } }

Percurso Pós-ordem

public void posOrdemRec(Node atual) { if (atual != null) { inOrdemRec(atual.getFilhoEsq()); inOrdemRec(atual.getFilhoDir()); System.out.println(atual.getNome()); } }

Procurando um Dado na Árvore

• Para encontrar um dado na árvore devemos percorrê-la usando um dos percursos vistos acima. Se encontrarmos o dado que estamos procurando iremos retornar o endereço do nó correspondente. Para inserirmos um dado na árvore antes de inserir o dado temos que ter o endereço do pai do dado, ou seja, temos que pesquisar o endereço do pai do dado caso este nó exista na árvore. O código seguinte é recursivo baseado em um percurso pré-ordem.

public boolean pesquisa(Node inicio, String procurado) { if (inicio != null) { if (procurado.equals(inicio.getNome())) { achou = inicio; return true; } pesquisa(inicio.getFilhoEsq(), procurado); pesquisa(inicio.getFilhoDir(), procurado); } return false; }

Remoção de Nós em Uma Árvore Binária

• A remoção de nós em uma árvore binária requer uma série de testes para determinar como os filhos dos nós serão reatados. As seguintes situações podem ocorrer:

• 1) O nó a ser removido não tem filhos, isto é, ele é uma folha, como é o caso do nó 35 da seguinte árvore. Neste caso basta atualizar o ponteiro do pai para que ele aponte para uma subárvore vazia:

Remoção de Nós em Uma Árvore Binária

2) O nó a ser removido tem somente um filho (esquerdo ou direito), como é o caso do nó 40 da seguinte figura. Neste caso fazemos com que o pai aponte para o filho do nó removido.

3) O nó a ser removido possui dois filhos. Nesse caso simplesmente não removemos o nó e acusamos um erro. Mais adiante veremos uma variação de uma árvore binária, a árvore binária de busca, onde existe uma informação extra que nos possibilita remover esse tipo de nó.

public boolean remove(Node no) { boolean resp = false; if (no != null) { if (no.filhoEsq != null) { If (no.filhoDir != null) { System.out.println("O nó não pode ser removido porque tem 2 filhos"); return false; } else //Situação em que só tem filho esquerdo { if (no == raiz) //Caso o nó seja raiz não pode testar o pai dela { raiz = no.filhoEsq; raiz.removePai(); return true; } else { char posicao = getPosNo(no); //Qual a posicao do no em relacao ao pai if (posicao == 'E') no.pai.filhoEsq = no.filhoEsq; else no.pai.filhoDir = no.filhoEsq; no.filhoEsq.pai = no.pai; return true; } } }//Fim de só ter filho esquerdo

else //Situação em que só tem filho direito ou mesmo filho nenhum {if (no == raiz) //Caso o nó seja raiz não pode testar o pai dela{raiz = no.filhoDir;if (!estaVazia()) raiz.removePai();return true;}else{ char posicao = getPosNo(no); if (posicao == 'E') no.pai.filhoEsq = no.filhoDir; else no.pai.filhoDir = no.filhoDir; if (no.filhoDir != null) no.filhoDir.pai = no.pai; return true;} } } return false; }

Exercícios propostos

1) Qual a altura mínima de uma árvore binária que possui 15 nós?2) a) Desenhar uma árvore binária que contenha 10 nós e tenha altura 5.    b) Desenhar uma árvore binária que contenha 14 nós e tenha altura 5.3) Uma árvore binária contém os seguintes valores: 1, 3, 7, 2, 12    a) Desenhar duas árvores de altura máxima contendo os dados.    b) Desenhar duas árvores completas onde o valor do pai é maior do que qualquer um dos seus filhos.4) Desenhar todas as possíveis árvores contendo 3 nós.

Exercícios Propostos

5) Dada a seguinte árvore, pede-se para percorrê-la usando os percursos pré-ordem, em ordem simétrica e pós-ordem.

Exercícios Propostos

6) Uma árvore binária tem 10 nós. Ao se percorrer a árvore em pré-ordem e em ordem simétrica, o resultado é o seguinte:

Pré-ordem: J C B A D E F I G HOrdem simétrica:   A B C E D F J G I HPede-se para desenhar a árvore.

7) Uma árvore binária tem 8 nós. Ao se percorrer a árvore em pós-ordem e em ordem simétrica, o resultado é o seguinte:

Pós-ordem: F E C H G D B AOrdem simétrica:   F C E A B H D GPede-se para desenhar a árvore.

8) Desenhar a árvore binária que representa a seguinte expressão aritmética:

(C + D + A * B) * (E + F)

Árvores Binárias de BuscaNos capítulos anteriores usamos listas lineares para armazenar informações. Nessas listas vimos que algumas operações eram bastante rápidas, com ordem O(1), enquanto que outras eram muito lentas, com ordem O(N). Em aplicações que envolvem grande quantidade de dados, o uso desses tipos de listas é proibitivo. O que precisamos é de uma estrutura que aproveita o que tem de melhor nas listas encadeadas e o que tem de melhor nas listas seqüenciais ordenadas. Essa estrutura é obtida com as árvores binárias de busca.

Uma árvore binária de busca é uma árvore binária onde, para qualquer nó, as seguintes propriedades são válidas:

• Todos os itens da subárvore esquerda são menores do que a raiz;

• Todos os itens da subárvore direita são maiores do que a raiz;

• Cada subárvore é por si mesma uma árvore binária de busca.

Exemplo de Árvore Binária de Busca

• A árvore é chamada de árvore de busca porque nós podemos seguir um caminho específico quando tentamos encontrar um elemento. Começando pela raiz, nós seguimos pela subárvore esquerda se o valor da chave for menor do que o valor do nó corrente. Caso contrário seguimos pela subárvore direita. Por exemplo, para encontrar o valor 40 na árvore anterior precisamos efetuar 4 comparações.

Operações em Uma Árvore Binária de Busca

A seguir mostramos a implementação das principais operações que podem ser feitas em uma árvore binária de busca.

Percursos na ÁrvoreOs algoritmos de percursos em uma árvore binária de busca são idênticos àqueles vistos no capítulo anterior. Por exemplo, tomando como referência a árvore da figura 9.1, obtemos as seguintes seqüências:

Pré-ordem: 50, 45, 35, 15, 5, 40, 38, 36, 42, 43, 46, 65, 75, 70, 85

Pós-ordem: 5, 15, 36, 38, 43, 42, 40, 35, 46, 45, 70, 85, 75, 65, 50

Simétrica: 5, 15, 35, 36, 38, 40, 42, 43, 45, 46, 50, 65, 70, 75, 85

Como podemos ver acima, o percurso na ordem simétrica nos dá uma seqüência muito útil para ser utilizada na vida prática, pois produz uma lista ordenada.

Operações em Uma Árvore Binária de Busca

• Método busque:• Usado para verificar se já existe a informação a ser inserida.

Caso não encontre a informação serão retornadas informações que permitirão que a nova informação seja inserida no local correto.

• O retorno deste método é um objeto da classe volta criado para este fim. Um objeto da classe Volta possui um boolean que informa se o nó foi encontrado ou não, um Node que retorna o endereço do nó em que a informação foi encontrada, caso ela foi encontrada e um char que indica – caso a informação não foi encontrada – se ela seria filho da esquerda ou da direita.

• Caso encontre a informação procurada é retornado o nó que a contém em getNo e true em achou. Caso não encontre a informação é retornado false em achou, getNo vai conter o pai do nó e getTF retorna um char informando se o filho vai ser da esquerda 'E' ou da direita 'D'.

Operações em Uma Árvore Binária de Busca

• Inserção de um nó na árvore:• Embora uma árvore binária de busca a rigor possa

ter nós com chaves repetidas, vamos admitir em nosso estudo que a chave seja única. Assim, para incluir um nó primeiro pesquisamos a existência do nó na árvore. Se existir, então acusaremos um erro, caso contrário incluímos o nó usando como pai o ponteiro retornado na rotina de busca.

Operações em Uma Árvore Binária de Busca

• Remover um nó da árvore:• Se o nó a ser removido for uma folha ou se tiver

somente um filho podemos usar o mesmo algoritmo apresentado para remover um nó nas árvores binárias, uma vez que, após a remoção, a árvore continua satisfazendo as condições de uma árvore binária de busca.

• Para o caso do nó ter dois filhos, como é o caso do nó 30 da árvore da figura seguinte, após a remoção desse nó, criamos duas subárvores órfãs que devem ser reatadas à árvore.

Operações em Uma Árvore Binária de Busca

• Nós iremos utilizar o seguinte algoritmo para reatar essas subárvores de modo que as propriedades da árvore binária de busca sejam mantidas:

Operações em Uma Árvore Binária de Busca

• Pesquisamos na subárvore esquerda do nó a ser removido o nó que possui o maior valor, no exemplo acima esse nó é o 28;

• Copiamos o maior valor no nó a ser removido

Operações em Uma Árvore Binária de Busca

• Finalmente removemos da árvore o nó que contém o maior valor. Como o maior valor de uma subarvore de busca sempre será sua ponta mais avançada a direita ele sempre será uma folha ou terá apenas um filho esquerdo, cairemos, portanto, nos casos anteriores.

Complexidade de uma Árvore Binária de Busca

• Quase todas as operações envolvendo árvores consiste em descer a árvore, nível a nível, até encontrar um nó particular. Quanto tempo leva para fazer isso? Se a árvore for cheia, como na figura abaixo cerca da metade de todos os nós da árvore se encontram no último nível (para ser mais exato existe na realidade um nó a mais no último nível do que no restante da árvore). Assim, cerca da metade de todas as buscas, inserções e remoções requerem a busca de um nó que se encontra no último nível.

Complexidade de uma Árvore Binária de Busca

• Durante o processo de busca nós precisamos visitar um nó em cada nível. Assim nós podemos ter uma idéia de quanto tempo leva esse processo, sabendo o número de níveis que a árvore possui.

• A relação entre o número de nós (N) e o número de níveis (L) em uma árvore cheia é dado por:

N = 2L - 1• ou seja

L = Log2(N+1)• Concluímos então que a complexidade das três operações

(busca, inclusão e remoção) para uma árvore binária de busca cheia é O(Log2N).

Complexidade de uma Árvore Binária de Busca

• Se a árvore não for cheia, a análise se torna difícil uma vez que em geral não se tem nenhuma idéia do formato que a árvore terá. A única certeza que teremos é que a complexidade será maior do que o da árvore cheia. Porém, quanto maior será?

• Antes de tudo é fácil descobrir o pior caso que acontece quando a árvore é construída a partir de chaves que chegam já ordenadas. Nesse caso a árvore se torna completamente degenerada, isto é, ela se transforma em uma lista linear, como mostrado na figura abaixo.

Complexidade de uma Árvore Binária de Busca

• No pior caso teremos de fazer em média N/2 comparações, ou seja sua complexidade é O(N).

• No entanto não podemos sempre ser otimista nem pessimista. Isto é, devemos fazer a análise considerando que as chaves chegam em ordem aleatória. Neste caso chega-se a seguinte fórmula que nos dá aproximadamente o número médio de comparações para encontrar uma chave específica:

• Número médio de comparações? 1,386 * Log2N• Podemos concluir então que, em média, o número de

comparações a serem feitas para encontrar um dado na árvore binária de busca é aproximadamente 38,6% maior do que para o melhor caso.

Árvores de busca AVL

• As árvores binárias foram projetadas para conseguirmos um acesso rápido aos dados entretanto dependendo da ordem de chegada dos dados a árvore pode ficar degenerada como podemos ver a seguir. Considere duas árvores Binárias de Busca obtidas de uma árvore vazia e da inserção dos números de 1 a 5. Na primeira árvore os números foram inseridos na ordem 2,1,4,3,5, na segunda foram inseridos na ordem 1, 2, 3, 4, 5.

Árvores de busca AVL

• Vemos que a segunda ABB é melhor que a primeira pois esta possui apenas 3 níveis ao passo que a primeira possui 5 níveis. Podemos dizer que a segunda é mais balanceada que a primeira. Esta situação fica ainda mais clara se inserirmos os números de 1 a 7 em ordem crescente e na ordem 4, 2, 6, 1, 3, 5, 7.

• Um problema das Árvores Binárias de Busca é que mesmo O(log n) sendo o tempo médio de processamento das operações de busca, inserção e remoção no pior caso ele sempre permanece O(n). Isso acontece em geral porque não sabemos nada sobre a forma da árvore. Se tivéssemos uma árvore balanceada poderíamos garantir ter O(log n).

Árvores de busca AVL

• Por esta razão, em 1962, dois matemáticos russos G. M. Adelson-Velskii e E. M. Landis criaram uma estrutura de árvore binária balanceada os quais denominaram de árvores AVL.

• Uma árvore AVL é uma árvore binária de busca onde a diferença entre as duas subárvores de cada nó é de no máximo uma unidade.

• As árvores AVL têm uma representação similar às árvores binárias de busca, sendo que as operações de inserção e remoção têm de monitorar constantemente as alturas das subárvores esquerda e direita do nó. Para manter essa informação iremos adicionar mais um campo no nó da árvore, chamado balanço, como mostrado abaixo.

Árvores de busca AVLclass Node{ String nome; Node filhoEsq; Node filhoDir; int Balanco; Node pai; . . .}

• O valor do balanço será a diferença entre as alturas das subárvores esquerda e direita:

• Balanço = Altura(Subárvore_esquerda) - Altura(Subárvore_direita)

• Se o valor do balanço for negativo indica que o nó está "mais pesado" no lado direito, enquanto que se seu valor for positivo, o nó está "mais pesado" no lado esquerdo. Se for zero o nó está equilibrado.

Árvores de busca AVL

• Em uma árvore AVL o balanço deve estar sempre no intervalo de -1 a 1. Como exemplo, a seguinte figura mostra duas árvores AVL com o valor dos balanços:

Inserção em Árvores de Busca AVL

• A inserção de um nó em uma árvore AVL é semelhante à inserção em uma árvore binária de busca. No entanto, a depender do local onde o nó será inserido, a árvore resultante pode deixar de ser balanceada. Por exemplo, a figura seguinte mostra a árvore resultante ao ser inserida a chave 5. Mesmo após a inserção, a árvore permaneceu balanceada.

Inserção em Árvores de Busca AVL

• Observe agora o que acontece ao ser inserida a chave 60 na mesma árvore:

• Neste caso a árvore resultante ficou desbalanceada.

Inserção em Árvores de Busca AVL

• A pista para saber se uma árvore vai continuar balanceada ou não após uma inserção é a seguinte:

• • Se no caminho onde o nó vai ser inserido todos os balanços têm o valor zero, a árvore resultante vai continuar balanceada;

• • Se o caminho onde o nó vai ser inserido existir algum balanço diferente de zero a árvore resultante continuará balanceada se a inserção for feita no lado "mais leve". Se a inserção for feita no lado "mais pesado" a árvore ficará desbalanceada.

Inserção em Árvores de Busca AVL• Como o ponto crítico em uma árvore AVL é a presença de

balanços diferente de zero, iremos dar atenção especial ao nó com balanço diferente de zero mais próximo do lugar onde o novo nó será inserido. Esse nó iremos denominar de pivô. Por exemplo, considere a árvore:

• Se formos inserir 15, o pivô será o nó 20, ao passo que se formos inserir 48, o pivô será o nó 45. Logo Pivô será o nó com balanço diferente de zero mais próximo do local onde o novo nó será inserido.

Inserção em Árvores de Busca AVL• Após cada inserção em uma árvore AVL temos

que verificar se algum nó ficou desbalanceado, isto é se a diferença de altura entre suas duas subarvores ficou maior que 1.

• Como iremos saber se a inserção de um nó vai desbalancear a árvore?– Se no caminho onde o nó vai ser inserido todos os

nós tem balanço igual a zero, ela vai continuar balanceada.

– Se no caminho onde o nó vai ser inserido tiver algum balanço diferente de zero, ela vai continuar balanceada se a inserção for feita no lado mais “leve”. Caso o nó seja inserido no lado mais “pesado” vai desbalancear.

Balanceando a árvore AVL

• Se a árvore ficar desbalanceada precisamos efetuar algumas transformações na árvore para que ela volte a ficar balanceada. Essas transformações são denominadas rotações, podendo ocorrer um dos seguintes casos:– Rotação simples para a direita– Rotação simples para a esquerda– Rotação dupla, uma para a esquerda outra para

a direita– Rotação dupla, uma para a direita e outra para a

esquerda

Balanceando a árvore AVL

• Rotação simples para a direita

• - Ocorre quando o pivô e seu filho esquerdo se tornam mais pesados do lado esquerdo

• ( O pivô torna-se filho direito de seu filho esquerdo, este toma o antigo lugar do pivô e quem era filho direito do filho esquerdo passa a ser filho esquerdo do que era o pivô).

Balanceando a árvore AVL

• Exemplo de rotação simples para a direita:

Balanceando a árvore AVL• Rotação simples para a esquerda• - Ocorre quando o pivô e seu filho direito ficam mais

pesados do lado direito.

• (O pivô torna-se filho esquerdo o que era seu filho direito e o que era filho esquerdo do filho direito passa a ser filho direito do pivô.)

Balanceando a árvore AVL

• Exemplo de rotação simples para a direita:

Balanceando a árvore AVL• Rotação dupla, uma para a esquerda outra para a direita

• - Ocorre quando o pivô se torna mais pesado do lado esquerdo e seu filho esquerdo se torna mais pesado do lado direito.

Balanceando a árvore AVL• (é feita uma rotação simples para a esquerda em torno do

filho do pivô seguida de uma rotação simples para a direita em torno do pivô).

• Exemplo de rotação dupla uma para a esquerda e outra para a direita:

Balanceando a árvore AVL• Rotação dupla, uma para a direita e outra para a esquerda

• - Ocorre quando o pivô se torna mais pesado do lado direito e seu filho direito se torna mais pesado do lado esquerdo

Balanceando a árvore AVL• (é feita uma rotação para a direita em torno do filho direito do

pivô seguida de uma rotação para a esquerda em torno do pivô.)

• Exemplo de uma rotação dupla, uma para direita e outra para esquerda

Remoção em Árvore AVL

• A remoção de um nó em uma árvore AVL pode também provocar seu desbalanceamento. Para rebalancear a árvore usamos um processo semelhante ao que foi usado na inserção, através do uso de rotações.

Complexidade de Árvores AVL

• A eficiência das árvores AVL depende da aplicação, uma vez que nós temos um esforço adicional para manter a árvore balanceada quando inserimos e removemos nós. Se essas operações forem executadas repetidamente, o tempo gasto será significante.

• Não existe o pior caso numa árvore AVL uma vez que sua estrutura se aproxima de uma árvore binária completa. Assim, a operação de busca será sempre de ordem O(Log2N). Evidências empíricas indicam que aproximadamente 50% das inserções e remoções requerem rotações. Concluímos então que as árvores AVL deverão ser usadas somente em situações onde a operação de busca é a operação dominante.

ExercíciosDadas as seguintes arvores AVL insira os elementos pedidos rebalanceando a arvore conforme necessário.1)Insira o elemento 5 na arvore abaixo 30 / \ / \ 10 40 / \ / \ 8 202) Insira o elemento 1 na arvore abaixo 10 / \ / \ 5 30 / \ / \ / \ / \ 3 8 20 40 / / 2

Exercícios3)Insira o elemento 53 na arvore abaixo 40 / \ / \ 32 46 / \ / \ 44 504) Insira o elemento 43 na arvore abaixo 40 / \ / \ 32 46 / \ / \ 44 50

• 5) Insira o elemento 4 na arvore abaixo• 10• / \• / \• 7 30• / \ / \• / \ / \• 5 8 20 40• /• /• 2• 6) Insira o numero 27 na arvore abaixo• 40• / \• / \• 20 60• / \ / \• / \ / \• 10 30 50 80• / / \• / / \• 5 25 90

Exercícios

7) Insira o numero 46 arvore abaixo 35 / \ / \ 30 40 / \ / \ / \ / \ 10 33 37 45 / \ \ / \ \ 5 38 47

Exercícios

Implementações em PascalO seguinte procedimento implementa o algoritmo que efetua uma rotação para direita em torno de um nó.procedure RotacaoParaDireita(No: PNoArvoreBin);{ Procedimento que efetua uma rotação para direita em torno do   nó apontado por No }var  Pai: PNoArvoreBin;begin   if No^.Filho[Esquerdo] = nil then      Exit;   Pai := No;   No  := No^.Filho[Esquerdo];   Pai^.Filho[Esquerdo] := No^.Filho[Direito];   if Pai^.Filho[Esquerdo] <> nil then      Pai^.Filho[Esquerdo]^.PPai := Pai;   No^.PPai := Pai^.PPai;   if No^.PPai^.Filho[Esquerdo] = Pai then      No^.PPai^.Filho[Esquerdo] := No   else      No^.PPai^.Filho[Direito] := No;   No^.Filho[Direito] := Pai;   Pai^.PPai := No;end;

O seguinte procedimento implementa as operações necessárias para efetuar uma rotação para esquerda em torno de um nó.procedure RotacaoParaEsquerda(No: PNoArvoreBin);{ Procedimento que efetua uma rotação para esquerda em torno do   nó apontado por No }var  Pai: PNoArvoreBin;begin   if No^.Filho[Direito] = nil then      Exit;   Pai := No;   No  := No^.Filho[Direito];   Pai^.Filho[Direito] := No^.Filho[Esquerdo];   if Pai^.Filho[Direito] <> nil then      Pai^.Filho[Direito]^.PPai := Pai;   No^.PPai := Pai^.PPai;   if No^.PPai^.Filho[Esquerdo] = Pai then      No^.PPai^.Filho[Esquerdo] := No   else      No^.PPai^.Filho[Direito] := No;   No^.Filho[Esquerdo] := Pai;   Pai^.PPai := No;end;

Implementações em Pascal

top related