• Nenhum resultado encontrado

Análise de Algoritmos

N/A
N/A
Protected

Academic year: 2021

Share "Análise de Algoritmos"

Copied!
41
0
0

Texto

(1)

Análise de Algoritmos

Referências Bibliográficas

[1] SZWARCFITER, J. L. & MARKENZON, L., Estruturas de Dados, LTC Editora, 1994.

[2] CORMEN, T. H., LEISERSON, C. E. & RIVEST, R. L., Introduction to

Algorithms.

Conteúdo Programático

Parte B – Análise de Algoritmos

B.1 – Algoritmos Ótimos B.2 – Listas Lineares B.3 – Árvores

B.4 – Árvores Binárias de Buscas B.5 – Árvores Balanceadas

Parte B – Análise de Estruturas de Dados

B.1 – Algoritmos Ótimos

• Complexidade relacionada a um dado algoritmo sem levar em consideração a possível existência de outros algoritmos para o mesmo problema.

• Limite inferior é uma função que limita inferiormente a complexidade de qualquer algoritmo que resolva um dado problema.

• Um algoritmo é ótimo quando a sua complexidade iguala um limite inferior obtido. Para limite inferior, utiliza-se a notação ΩΩΩΩ.

(2)

Exemplo: Inversão de seqüência

Ler a seqüência → Ω(n); complexidade O(n) ótimo

Exemplo: Soma de matrizes

Ler a matriz →Ω(n2); complexidade O(n2) ótimo

Exemplo: Soma de matrizes

Ler a matriz →Ω(n2); complexidade O(n3) sabe-se que não é ótimo

B.2 – Listas Lineares

• Uma lista linear agrupa informações referentes a um conjunto de elementos que, de alguma forma, se relacionam entre si.

• Uma lista linear, ou tabela, é então um conjunto de n ≥≥≥≥ 0 nós L[1], L[2], ...,

L[n] tais que suas propriedades estruturais decorrem, unicamente, da posição

relativa dos nós dentro da seqüência linear. Tem-se:  Se n > 0, L[1] é o primeiro nó;

 Para 1 < k ≤≤ n, o nó L[k] é precedido por L[k-1].

• As operações mais recentes em listas são a busca, inclusão e remoção de um determinado elemento.

B.2.1 – Alocação Sequencial

• Nesse tipo de alocação, as listas são armazenadas em posições contínuas de memória, reservadas estaticamente.

• Para controlar a inserção e retirada dos elementos na lista, empregam-se variáveis apontadoras para os índices do vertor que armazena a lista.

(3)

nó 1 nó 2 nó 3 nó 4

chave nome endereço L L+c L+2c L+3c

Algoritmo de busca de um elemento na lista L Função busca1(x) i := 1; busca1:= 0; enquanto i ≤≤ n faça se L[i].chave = x então busca1 := i; i := n + 1; então i := i + 1;

• No algoritmo anterior dois testes são feitos: fim de lista e chave procurada. No sentido de se evitar o teste de fim de lista poderia colocar o elemento procurado depois do fim da lista (posição n + 1).

Algoritmo de busca de um elemento na lista L Função busca(x)

L[n+1].chave := x; i := 1;

enquanto L[i].chave != x faça i := i + 1; se i != n+1 então

busca := i; senão busca := 0;

• As complexidades de pior caso dos algoritmos anteriores é O(n) e as suas complexidades médias também são iguais. Para calculá-las, seja q a probabilidade de sucesso na busca, seja Ei, 1 ≤≤≤≤ i ≤≤ n, uma entrada em que a

chave procurada ocupa a i-ésima posição da lista e seja E0 a entrada que

(4)

≤ ≤ ≤ ≤

+

=

+

=

=

=

=

=

n k k n k k k k k

q

n

q

k

n

q

n

q

E

t

E

p

n

E

t

n

k

k

E

t

q

E

p

n

k

n

q

E

p

0 1 0

]

)

2

)[(

2

1

(

)

(

)

1

(

)

(

)

(

,

)

(

1

,

)

(

,

1

)

(

1

,

)

(

• Como casos particulares, se q = 1, isto é, a chave se encontra sempre na lista, então a complexidade é ≈≈≈ n/2. Se q = 1/2, essa cresce para ≈≈ ≈≈≈ 3n/4. Se q = 0, isto é, todas as buscas são sem sucesso, a complexidade média atinge o valor

n.

Algoritmo de busca de um elemento na lista ordenada L Função busca-ord(x)

L[n+1].chave := x; i := 1;

enquanto L[i].chave < x faça i := i + 1; se i = n+1 ou L[i].chave != x então

busca-ord := 0; senão busca-ord := i;

• A complexidade de pior caso do algoritmo acima é igual à complexidade dos algoritmos anteriores, porém a complexidade média é diferente, pois deve-se considerar 2n + 1 entradas diferentes, pois o insucesso pode ser detectado em

n + 1 situações distintas.

• Para fazer o cálculo da complexidade média, é necessário definir R0, ..., Rn

conjuntos de elementos não pertencentes à lista, isto é, R0 representa todos os

valores possíveis menores do que a primeira chave de L, Rn corresponde aos

valores maiores do que a última chave de L, enquanto Rk, 1 ≤≤ k < n, é o

conjunto dos valores maiores do que a k-ésima e menores do que a (k+1)-ésima chave de L.

As 2n + 1 entradas distintas podem ser descritas como:

(5)

E`k = entrada em que a chave procurada pertence a Rk, 0 ≤≤ k ≤≤ ≤≤≤ n. 2 ) 2 ( ) 1 ( ] ) 1 ( ) 1 ( [ ) ( ) ( ) ( ) ( ) ( 0 , 1 ) ( 1 , ) ( 0 ) 1 ( ) 1 ( ) ( 1 , ) ( 0 1 0 1 + − = = + ⋅ + − + ⋅ = = ′ ′ + ≤ ≤ + = ′ ≤ ≤ = ≤ ≤ + − = ′ ≤ ≤ =

= = = = q n k n q k n q E t E p E t E p n k k E t n k k E t n k n q E p n k n q E p n k n k n k k k n k k k k k k k

• A complexidade média do algoritmo de busca na lista ordenada é aproximadamente n/2 para qualquer probabilidade q.

Algoritmo de busca binária Função busca-bin(x)

Inf := 1; sup := n; busca-bin := 0; enquanto inf ≤≤≤≤ sup faça

meio := PISO ((inf+sup)/2); se L[meio].chave = x então

busca-bin := meio; inf := sup + 1;

senão se L[meio].chave < x então inf := meio + 1;

senão sup := meio – 1;

• O pior caso da busca binária ocorre quando o elemento procurado é o último a ser encontrado, ou mesmo não é encontrado, isto é quando a busca prossegue até a tabela se resumir a um único elemento.

 na primeira iteração, a dimensão da tabela é n;

 na segunda iteração, a dimensão da tabela é PISO(n/2);  na terceira iteração, a dimensão da tabela é PISO((n/2)/2);

(6)

 na m-ésima iteração, a dimensão da tabela é 1;

• O número de iterações é, no máximo, 1 + PISO(log2n). O tempo consumido

pelas operações em cada iteração é constante. Logo, a complexidade da busca binária é O(logn).

B.2.2 – Alocação Encadeada

• Na alocação encadeada (alocação dinâmica), os nós das listas reservam a quantidade de memória necessária ao seu armazenamento conforme são criados e, após a sua destruição, a porção de memória destinada ao nó é liberada para uma nova utilização.

nó 2

a b c d

nó 1 nó 3 nó 4

• A alocação encadeada, apesar de gastar memória adicional com um campo a mais nos nós das listas, é mais conveniente quando o problema inclui o tratamento de mais de uma lista. Entretanto, o acesso ao k-ésimo elemento é imediato na alocação seqüencial.

B.2.2.1 – Listas Simplesmente Encadeadas

• Todas as estruturas de dados com alocação encadeada necessitam de um ponteiro para o primeiro nó. No caso das listas simplesmente encadeadas existe apenas um único campo de ponteiro que armazena o endereço do próximo nó da lista, sendo que o último nó aponta para um nó nulo (inexistente), representado por nil.

Algoritmo de busca em uma lista ordenada Procedimento busca-enc(x, ant, pont)

ant := ptlista; pont := nil; ptr := ptlista^.prox; enquanto ptr != nil faça

se ptr^.chave < x então ant := ptr;

ptr := ptr^.prox;

senão se ptr^.chave = x então pont := ptr; ptr := nil;

(7)

• No algoritmo anterior, o parâmetro ant não tem função, mas nos procedimentos de inserção e remoção ele terá uma importância grande. A complexidade desse algoritmo é O(n), pois o mesmo percorre a lista cujo tamanho é n.

• Os algorimos de inserção e remoção de um elemento em uma lista encadeada é feito a partir do algoritmo acima, seguidos dos passos mostrados a seguir. A complexidade dessas operações é, em virtude da busca, O(n).

se pont = nil então ocupar (pt);

pt^.info := novo_valor; pt^.chave := x;

pt^.prox := ant^.prox; ant^.prox := pt;

senão “elemento já está na tabela”;

se pont != nil então

ant^.prox := pont^.prox; valor_lido := pont^.info; desocupar (pont);

senão “nó não está na tabela”;

B.2.2.2 – Pilhas e Filas

• Considerando-se listas simplesmente encadeadas, o topo da pilha é o primeiro nó da lista, apontado por uma variável ponteiro topo. Filas exigem duas variáveis do tipo ponteiro: inicio, que aponta para o primeiro nó da lista e fim, que aponta para o último.

Algoritmo de Inserção na Pilha Algoritmo de Remoção da Pilha Ocupar (pt);

pt^.info := novo_valor; pt^.prox := topo;

topo := pt;

se topo != nil então pt := topo;

topo := topo^.prox; valor_lido := pt^.info; desocupar (pt);

senão underflow;

Algoritmo de Inserção na Fila Algoritmo de Remoção da Fila ocupar (pt);

pt^.info := novo_valor; pt^.prox := nil;

se fim != nil então fim^.prox := pt; senão inicio := pt; fim := pt;

se inicio != nil então pt := inicio;

inicio := inicio^.prox;

se inicio = nil então fim := nil; valor_lido := pt^.info;

desocupar (pt); senão underflow;

(8)

• As complexidades dos algoritmos de manipulação de filas e pilhas é constante, ou seja, O(1), uma vez que buscas não são empregadas.

B.2.2.3 – Listas Duplamente Encadeadas

• Nesse tipo de lista linear, utiliza-se um ponteiro para o nó anterior além do ponteiro que todo nó já possui para o próximo nó. Embora haja um gasto adicional de memória imposto por um novo campo de ponteiro, isso pode ser justificado pela economia em não processar praticamente a lista inteira em algumas aplicações, pois a lista pode ser percorrida nos dois sentidos.

nó 2

a b z

nó 1 ... nó n ptlista

Algoritmo de busca em uma lista duplamente encadeada ordenada Função busca-dup(x)

ultimo := ptlista^.ant;

se x <= utlimo^.chave então pont := ptlista^.prox;

enquanto pont^.chave < x faça pont := pont^.prox; busca-dup := pont;

senão busca-dup := ptlista; Algoritmo de Inserção em uma lista

duplamente encadeada Algoritmo de Remoção de uma lista duplamente encadeada pont := busca-dup(x); se pont = ptlista ou pont^.chave != x então anterior := pont^.ant; ocupar (pt); pt^.info := novo_valor; pt^.chave := x; pt^.ant := anterior; pt^.prox := pont; anterior^.prox := pt; pont^.ant := pt;

senão elemento já está na lista;

pont := busca-dup(x); se pont != ptlista e pont^.chave = x então anterior := pont^.ant; posterior := pont^.prox; anterior^.prox := posterior; posterior^.ant := anterior; valor_lido := pont^.info; desocupar (pont);

(9)

B.3 – Árvores

• Uma árvore enraizada T, ou simplesmente árvore, é um conjunto finito de elementos denominados nós ou vértices tais que:

 T = φφφφ, e a árvore é dita vazia, ou

 existe um nó especial, ττττ, chamado raiz de T; os restantes constituem um único conjunto vazio ou são divididos em m ≥≥≥≥ 1 conjuntos disjuntos não vazios, as subárvores de ττττ, ou simplesmente subárvores, cada qual por sua vez uma árvore.

• Uma floresta é um conjunto de árvores. Se υυυ é um nó de T, a notação Tυ υυυυ

indica a subárvore de T com raiz υυυυ.

• A principal representação das árvores é através dos diagramas hierárquicos.

A D C B E F G H I

• Seja υυυ o nó raiz da subárvore Tυ υυυυ de T. Os nós ωωωω1, ωωωω2, ..., ωωωωj das subárvores

de Tυυ υυsão chamados filhos de υυυ; υυ υυυ é chamado pai de ωωωω1, ωωωω2, ..., ωωωωj. Os nós ωωωω1,

ω ωω

ω2, ..., ωωωωj são irmãos.

• número de filhos de um nó é chamado grau de saída desse nó.

• Se x pertence à subárvore Tυυυυ, x é descendente de υυυυ, e υυυ é ancestral de x. υ

Caso x seja diferente de υυυυ, x é descendente próprio de υυυ, e υυ υυυ é ancestral

(10)

• Toda árvore com n > 1 nós possui no mínimo 1 e no máximo n – 1 folhas. Um nó não folhas é dito interior.

• A altura de um nó υυυ é o número de nós do maior caminho de υυ υυ até um de υ seus descendentes. As folhas têm altura 1. A altura da árvore T é igual ao nível máximo de seus nós. Representa-se a altura T por h(T).

• Uma árvore ordenada é aquela na qual os filhos de cada nó estão ordenados. Duas árvores não ordenadas são isomorfas quando puderem se tornar coincidentes através de uma permutação na ordem das subárvores de seus nós.

B.3.1 – Árvores Binárias

• Uma árvore binária T é um conjunto finito de elementos denominados nós ou

vértices, tal que

 T = φφφφ, e a árvore é dita vazia, ou

 existe um nó especial, ττττ, chamado raiz de T; e os restantes podem ser divididos em dois subconjuntos disjuntos, TEττττ TDττττ, a subárvore esquerda

e a direita de ττττ, respectivamente, às quais são também árvores binárias.

A E C B F H I D G

• Toda árvore binária com n nós possui exatamente n + 1 subárvores vazias entre suas subárvores esquerda e direitas.

(11)

• Uma árvore estritamente binária é uma árvore binária em que cada nó possui zero ou dois filhos. Uma árvore binária completa é aquela que apresenta a seguinte propriedade: se υυυ é um nó tal que alguma subárvore de υυ υυυ é vazia, então υυυυ se localiza ou no último ou no penúltimo nível da árvore.

• Uma árvore binária cheia é aquela em que, se υυυυ é um nó com alguma de suas subárvores vazias, então υυυυ se localiza no último nível. Toda árvore binária cheia é completa e estritamente binária.

• A árvore binária que possui altura máxima é aquela cujos nós interiores possuem exatamente uma subárvore vazia. Uma árvore completa sempre apresenta altura mínima.

• Seja T uma árvore binária completa com n > 0 nós. Então T possui altura h mínima. Além disso, h = 1 + PISO(log n).

• O armazenamento das árvores pode ser estático ou dinâmico. Em cada nó da árvore é necessário campos para armazenar ponteiros para seus filhos.

B.3.2 – Percurso em Árvores Binárias

• Para percorrer uma árvore deve-se visitar cada um de seus nós. Visitar um nó significa operar de alguma forma a informação do nó.

• Um algoritmo de percurso de uma árvore T é composto de três passos: visitar a raiz de T; percorrer a subárvore esquerda de T; e percorrer a subárvore direita de T. Esses passos podem ser executados em ordens diferentes conforme apresentado a seguir:

• O percurso em pré-ordem segue recursivamente os seguintes passos, para cada subárvore da árvore:

 visitar a raiz;

 percorrer sua subárvore esquerda, em pré-ordem;  percorrer sua subárvore direita, em pré-ordem.

• Na árvore da figura anterior, o percurso em pré-ordem seria A B D G C E H

(12)

Algoritmo de percurso em pré-ordem procedimento pre(pt)

visita (pt);

se pt^.esq != nil então pre(pt^.esq); se pt^.dir != nil então pre(pt^.dir); se ptraiz != nil então pre(ptraiz)

• O percurso em ordem simétrica é composto pelos seguintes passos:  percorrer sua subárvore esquerda, em ordem simétrica;

 visitar a raiz;

 percorrer sua subárvore direita, em ordem simétrica.

Algoritmo de percurso em ordem simétrica procedimento simet(pt)

se pt^.esq != nil então simet(pt^.esq); visita (pt);

se pt^.dir != nil então simet(pt^.dir); se ptraiz != nil então simet(ptraiz)

• Na árvore da figura anterior, o percurso em ordem simétrica seria D G B A H

E I C F.

• Uma terceira alternativa de percurso seria o percurso em pós-ordem que possui os seguintes passos:

 percorrer sua subárvore esquerda, em pós-ordem;  percorrer sua subárvore direita, em pós-ordem;  visitar a raiz.

• Na árvore da figura anterior, o percurso em pós-ordem seria G D B H I E F

C A.

Algoritmo de percurso em pós-ordem procedimento pos(pt)

se pt^.esq != nil então pos(pt^.esq); se pt^.dir != nil então pos(pt^.dir); visita (pt);

(13)

• Nos três casos de algoritmos de percurso apresentados, a complexidade de pior caso é O(n), onde n é o número de nós da árvore.

• Uma aplicação para o percurso em pós-ordem seria o cálculo da altura de um determinado nó de uma árvore binária. No algoritmo a seguir, a altura é um campo dos nós da árvore.

Algoritmo para calcular a altura de um nó da árvore binária procedimento visita(pt)

se pt^.esq != nil então

alt1 := (pt^.esq)^.altura senão alt1 := 0;

se pt^.dir != nil então

alt2 := (pt^.dir)^.altura senão alt2 := 0;

se alt1 > alt2 então

pt^.altura := alt1+1; senão pt^.altura := alt2+1;

B.4 – Árvores Binárias de Busca

B.4.1 – Conceitos da Árvore Binária de Busca

• Seja S = {s1, s2, ...., sn} o conjunto de chaves que satisfaz s1 < s2 < ... < sn.

Seja x um valor dado com o objetivo de verificar se x ∈∈∈∈ S, ou seja qual j tal que sj = x, caso exista.

• Para resolver esse problema, deve-se empregar uma árvore binária T com as seguintes características:

 T possui n nós. Cada nó υυυ corresponde a uma chave distinta sυ j∈ S e

possui como rótulo o valor r(υυυυ) = sj.

 Seja um nó υυυυ de T. Seja também υυυυ1, pertencente à subárvore esquerda de

υ υυ

υ. Então r(υυυυ1) < r(υυυ). Analogamente, se υυ υυυ2 pertence à subárvore direita de

υ υυ

(14)

• A árvore T denomina-se árvore binária de busca para S. Pode existir várias árvores de busca para um mesmo S.

1 6 2 5 7 3 4 7 2 1 3 6 5 4

• algoritmo a seguir apresenta a busca de um nó em uma árvore binária de busca em que a variável f denota o resultado da busca, isto é:

 f = 0, se a árvore é vazia.

 f = 1, se x ∈∈ S. Nesse caso, pt aponta para o nó procurado.  f > 1, x ∉∉ S.

Algoritmo de busca em árvore binária de busca procedimento busca-arvore(x, pt, f)

se pt = nil então f := 0;

senão se x = pt^.chave então f := 1 senão se x < pt^.chave então

se pt^.esq = nil então f := 2 senão pt := pt^.esq

busca-arvore (x,pt,f) senão se pt^.dir = nil então f := 3

senão pt := pt^.dir

busca-arvore (x,pt,f) pt := ptraiz; busca-arvore (x,pt,f);

• A complexidade de pior caso do algoritmo de busca acima é O(n), onde n é o número total de nós.

(15)

• É possível observar que a complexidade de pior caso do algoritmo de busca anterior é igual à altura da árvore, logo, a eficiência do pior caso do algoritmo será tão maior quanto menor for a altura da árvore.

• Conclui-se, então, que é importante construir árvores com altura mínima. A árvore completa possui tal propriedade. A complexidade do algoritmo, nesse caso, passaria a ser igual a O(n).

• Lema: Seja T, uma árvore binária completa com n nós e altura h. Então

2h - 1≤ n ≤≤ ≤≤≤ 2h - 1

Algoritmo de inserção em árvore binária de busca pt := ptraiz; busca-arvore (x,pt,f);

se f = 1 então “inserção inválida” senão ocupar (pt);

pt1^.chave := x; pt1^.info := novo_valor; pt1^.esq := nil; pt1^.dir := nil;

se f = 0 então ptraiz := pt1 senão se f = 2 então

pt^.esq := pt1; senão pt^.dir := pt1;

• A complexidade do algoritmo de inserção é a mesma do procedimento busca-arvore.

• Para construir uma árvore binária de busca, pode-se utilizar o algoritmo anterior aplicado a cada nó de uma seqüência S. Dependendo dos elementos da seqüência S, a árvore de busca binária montada pode ter altura n. A complexidade da construção de uma árvore de busca binária, baseada na aplicação do algoritmo anterior, é O(n2).

• Para construir uma árvore de busca completa, basta aplicar a construção anterior ao conjunto de chaves convenientemente reordenado. Sejam s0 e sn+1

duas chaves fictícias e definidas como já inseridas na árvore final T.

• A idéia consiste em, a cada passo, inserir em T alguma nova chave que seja de índice médio entre duas chaves si e sj, já inseridas em T, e tais que

nenhuma outra chave tenha sido ainda inserida entre estas duas. Essa nova chave torna-se já inserida, e repete-se o processo. Além de obter uma árvore completa, ou seja, mais conveniente para a operação de busca, essa

(16)

construção é também mais eficiente que a anterior. Sua complexidade é O(n

logn).

• A utilização da árvore binária de busca completa é muito conveniente caso as freqüências de acesso aos dados sejam constantes, no entanto, no caso geral, seria mais importante encontrar a árvore de busca binária que fosse específica para as freqüências de uma aplicação.

• O número de comparações necessárias para achar um elemento sj em uma

árvore de busca binária a partir do algoritmo de busca apresentado é igual ao nível lk de sk.

• O número total de comparações para visitar todos os elementos de S é dado por:

• O valor acima é denominado de comprimento de caminho interno I(T) da árvore T.

≤ ≤k n k

l

1

• Na árvore da esquerda da figura anterior, o comprimento de caminho interno é igual a 1 + 2 + 2 + 3 + 3 + 4 + 4 = 19.

• O I(T) denota o total de comparações de buscas realizadas com sucesso, isto é, aquelas que acessam elementos de S. Para se modelar o acesso a elementos não pertencentes a S, seja o conjunto R das chaves x buscadas ao longo de toa a computação.

R0 = {x ∈∈ R | x < s1}

Rn = {x ∈∈ R | x > sn}

Rj = {x ∈∈∈∈ R | sj < x < sj+1} j = 1, ...., n – 1

• Os n + 1 conjuntos Rj, 0 ≤≤≤≤ j ≤≤ n, representam intervalos onde se localizam as chaves correspondentes às buscas sem sucesso. Os intervalos Rj ocorrem da esquerda para a direita ao final da árvore, pois uma busca sem sucesso termina em uma subárvore vazia.

(17)

1 6 2 5 7 3 4 R0 R1 R2 R3 R4 R5 R6 R7

• Os nós da árvore binária de busca referentes aos intervalos Rj são chamados

de externos, enquanto os outros são chamados de internos.

• O número de comparações correspondente a uma busca sem sucesso é dado por l’k – 1, onde l’k é o nível de Rk (nó k em que a busca sem sucesso

termina). O número total de comparações, considerando todos os nós externos é, então, igual a:

≤ ≤

n k k

l

0

)

1

(

• O valor acima é denominado comprimento de caminho externo de T, representado por E(T).

• A árvore da figura anterior possui comprimento de caminho externo igual 2 +

2 + 3 + 3 + 4 + 4 + 4 + 4 = 26.

• Os valores I(T)/n e E(T)/(n + 1) representam os números médios de comparações efetuadas em operações de busca, com e sem sucesso, respectivamente.

• No caso da árvore anterior, são necessárias, em média, 19/7 = 2,71 comparações para localizar uma chave, ou 26/8 = 3,25 comparações para concluir que uma chave não está presente.

(18)

B.4.2 – Buscas com Freqüências de Acesso Diferenciadas

• Seja T uma árvore binária de busca, em que cada chave sk possui freqüência

de acesso fk e se localiza em um nível lk de T, 1 ≤≤≤≤ k ≤≤≤≤ n. A árvore T também

incorpora nós externos (R0, ..., Rn) onde terminam as buscas sem sucesso.

Cada Rk possui uma freqüência de acesso f’k e se localiza no nível l’k de T,

0 ≤≤≤≤ k ≤≤≤≤ n.

• A parcela de contribuição de sk para uma estimativa do número total de

comparações realizadas ao longo do processo é igual a fklk. Considerando

todas as chaves, o número total de comparações é:

≤ ≤k n k k

l

f

1

• O valor acima é denominado de comprimento de caminho interno

ponderado de T.

• Analogamente, para o caso das buscas sem sucesso, a parcela de contribuição de Rk, no total de comparações efetuadas é f’k(l’k – 1). Considerando todos os

nós externos, o número total de comparações é:

≤ ≤

n k k k

l

f

0

)

1

(

• Esse somatório denomina-se comprimento de caminho externo ponderado de T.

• O número total de comparações efetuadas ao longo do processo, considerando-se buscas com e sem sucesso, denomina-se custo c(T), ou seja:

≤ ≤ ≤ ≤

+

=

n k k k n k k k

l

f

l

f

T

c

0 1

)

1

(

)

(

• A árvore ótima é aquela que apresenta custo mínimo e para encontrá-la, aplica-se uma técnica denominada programação dinâmica. A idéia consiste em decompor o problema dado em dois outros de tamanho menor, isto é, correspondentes a subconjuntos próprios de S.

(19)

• Seja sk a raiz de T, representam-se por T’ e T’’ as subárvores esquerda e

direita de sk, respectivamente (ver figura).

sk

T'' T'

• T’ é uma árvore binária de busca para o conjunto de chaves {s1, ..., sk-1},

contendo os nós externos R0, ..., Rk-1. T’’ é uma árvore para as chaves {sk+1,

..., sn} com os nós externos Rk, ..., Rn.

• Lema: As subárvores de uma árvore binária de busca ótima são também ótimas.

• Pelo lema anterior, uma vez conhecida a raiz sk e sabendo-se como

determinar T’ e T’’, a árvore T estará construída.

• Na ausência de um método direto que forneça a raiz sk, podem-se tentar todas

as possibilidades, pois o número total de subproblemas a tratar não seria excessivo.

• Qualquer chave pode ser a raiz de T. A idéia é tentar cada uma delas, pois são apenas n possibilidades. Para construir T’ e T’’, escolhe-se para raiz uma chave pertencente a {s1, ..., sk-1} e constroem-se as suas respectivas

subárvores ótimas de forma recursiva. O processo continua até que cada subconjunto de chaves seja tão pequeno que o subproblema correspondente se torne trivial e a sua solução seja imediata.

• Seja T(i,j) a árvore ótima correspondente ao subconjunto de chaves {si+1, ...,

sj}, 0 ≤≤≤≤ i ≤≤≤≤ j ≤≤≤≤ n. Seja F(i,j) a soma de todas as freqüências correspondentes a

T(i,j), isto é:

≤ ≤ ≤ <

+

=

j k i k j k i k

f

f

j

i

F

(

,

)

• Lema: Seja T(i,j) a árvore binária de busca ótima de raiz sk correspondente

(20)

)

,

(

))

,

(

(

))

1

,

(

(

))

,

(

(

T

i

j

c

T

i

k

c

T

k

j

F

i

j

c

=

+

+

• A partir da relação anterior, pode-se montar o algoritmo para a construção da árvore ótima. Basta calcular c(T(0,n)) recursivamente, sabendo-se que os valores iniciais (isto é, o fim da recursão), que correspondem às subárvores vazias, são iguais a:

n

i

i

i

T

c

(

(

,

))

=

0

0

• A computação recursiva de c(T(0,n)) possui a desvantagem de não reconhecer a ocorrência de subproblemas repetidos no processo, pois um mesmo custo c(T(i,j)) seria recalculado diversas vezes no processo sem necessidade.

• Como o número total de subproblemas produzidos é exponencial, pode-se concluir que a complexidade do algoritmo correspondente ao cálculo anterior, é também exponencial.

• Para eliminar o cálculo repetitivo, pode-se armazenar os custos calculados em uma tabela. O número total de custos distintos é n(n + 1)/2, ou seja, um para cada par de valores i, j satisfazendo 0 ≤≤≤≤ i < j ≤≤≤≤ n.

• Seguindo a nova estratégia, é necessário calcular o custo de forma não recursiva. Para computar c(T(i,j)) é necessário que já estejam calculados todos os valores que aparecem do lado direito de sua expressão.

• A computação de F(i,j) é independente da de c(T(i,j)). As computações de

c(T(i,k – 1)) e c(T(k,j)) devem ser feitas antes do início da computação de c(T(i,j)). Para isso, basta observar que a diferença de seus índices é sempre

estritamente menor do que o correspondente a esse último valor, ou seja,

(k - 1) – i < j – i e j – k < j – i. Logo, computam-se os valores de c(T(i,j)) em

ordem não crescente de diferença de índices.

• No algoritmo a seguir, as variáveis c e f são matrizes de dimensão

(n + 1)××××(n + 1) e são dados os valores f1, ..., fn e f’0, f’1, ..., f’n. O custo da

(21)

Algoritmo do cálculo do custo da árvore binária ótima para j := 0 até n faça

c[j,j] := 0; F[j,j] := f’j; para d := 1 até n faça

para i := 0 até n – d faça j := i + d;

F[i,j] := F[i,j – 1] + fj + f’j;

c[i,j] := mini<k≤≤≤≤j {c[i,k – 1] + c[k,j]} + F[i,j];

• A complexidade desse algoritmo é O(n3), pois a execução da última linha do algoritmo, correspondente ao cálculo de c[i,j] por si só demanda tempo O(n), sendo executada O(n2) vezes.

• Existe uma variação desse algoritmo, baseada no princípio da

monotonicidade da árvore binária de busca que reduz a sua complexidade

para O(n2). Esse princípio afirma que se sk é a raiz de uma árvore ótima de

um conjunto {si, ..., sj} e uma nova chave sj+1 é agregada ao conjunto, então

existe uma nova árvore ótima para o conjunto {si, ..., sj, sj+1} com a chave sq,

q ≥≥≥≥ k em sua raiz. Analogamente, se a chave si-1 for agora agregada, há uma

árvore ótima para {si-1, si, ..., sj} com sq, q ≤≤ k na raiz.

• O princípio acima reduz a quantidade de candidatos a figurar nas raízes das árvores ótimas correspondentes aos diversos subproblemas do processos. • Para construir a árvore binária de busca ótima, deve-se armazenar o valor

minimizante k do cálculo do custo c[i,j]. É necessário o uso de uma outra matriz para armazenar os valores de k.

• Para acompanhar o funcionamento do algoritmo anterior, será levado em conta um caso com n = 4.

j 0 1 2 3 4 fj - 10 1 3 2

(22)

=

1

4

1

8

5

1

10

7

3

1

22

19

15

13

2

F

=

0

4

0

12

5

0

17

10

3

0

39

29

18

13

0

c

=

4

3

3

1

3

3

2

1

1

1

k

s1 s4 s3 s2 R0 R1 R2 R3 R4

B.4.3 – Árvores de Partilha

• Dado um conjunto S = {s1, ..., sn} e um chave procurada x. O problema

consiste em encontrar um índice j tal que x = sj. Cada si do conjunto S possui

uma freqüência de acesso fi. A busca será feita com a utilização de uma

árvore binária de partilha T.

• Cada nó q de T de armazena uma chave do conjunto S, denominada de chave real (real (q)). Além disso, o nó q possui também uma segunda chave,

(23)

denominada chave de partilha (partilha (q)) a qual será usada para determinar o caminho a ser seguido na busca (esquerdo ou direito).

c b g g b b f e d d a c e e chave real chave de partilha

Algoritmo de busca em árvore binária de partilha procedimento busca-partilha (x, pt)

se pt = nil então “a chave não se encontra no conjunto”

senão se x = pt^.real então “a chave está no nó apontado por pt” senão se x <= pt^.partilha então

pt := pt^.esq;

busca-partilha (x,pt); senão pt := pt^.dir;

busca-partilha (x,pt); pt := ptraiz; busca-partilha(x,pt);

• O valor da chave de partilha de cada nó pode ser, em princípio, qualquer elemento de S e não está restrito às chaves que formam a subárvore do nó considerado. A chave de partilha de qualquer folha é sempre irrelevante e pode ter qualquer valor.

Dado um conjunto S = {s1, ..., sn} e cada si de S possui freqüência fi, para

construir a árvore binária de partilha ótima, será levado em conta algumas definições a seguir: 1o) Custo da árvore T

≤ ≤

=

n i i i

l

f

T

c

1

)

(

(24)

onde li é o nível de si em T.

2o) Alcance de uma subárvore Tυυυ. υ

O alcance de uma subárvore Tυυυ de raiz υυ υυυ é um par ordenado, definido recursivamente da seguinte maneira:

 Se υυυυ = raiz(T), então alcance(Tυυυυ) = [1,n].

 Caso contrário, seja ωωωω o pai de υυυυ, suponha que alcance(Tωω) = [iωω ωωωω,jωωωω] e que sk é a chave de S selecionada como nó de partilha para o nó ωωω. Logo, se υω υυυ

é filho esquerdo de ωωω, define-se alcance(Tυω υυυ) = [iωωωω,k]; se υυυυ for filho

direito, então alcance(Tυυυυ) = [k+1,jωωωω].

• Uma conseqüência da definição de alcance é que se alcance(Tυυυυ) = [i,j], então toda chave sp que está incluída em Tυυυ se encontra no intervalo [i,j], υ

mas o contrário não vale necessariamente, isto é, nem toda árvore pertencente ao intervalo [i,j] pertence à subárvore Tυυυυ, pois Tυυυ pode conter υ menos que j – i + 1 nós, vindo a sobrar algumas chaves do intervalo. A figura a seguir mostra os alcances para os nós da árvore anterior.

[1,3] [6,7] [1,2] [5,7] [4,7] [1,7] [5,5] Supor que as chaves

a, ..., g correspondem aos índices 1, ..., 7, respectivamente.

• Lema: As subárvores de uma árvore binária de partilha ótima são também ótimas.

• Lema: Seja T uma árvore binária de partilha ótima e υυυ, um nó de T. A chave υ real que υυυ contém é aquela de menor freqüência dentre as compreendidas no υ intervalo alcance(Tυυυυ) e que não figura em qualquer nó de Tυυυυ.

(25)

• O lema anterior trata-se de um critério para se escolher a chave real dos nós, porém não existe critério para se escolher as chaves de partilha, devem ser experimentadas todas as possibilidades.

• Denota-se por c(i,j,d) o custo da subárvore binária ótima de partilha T(i,j,d), contendo as chaves si+1, ..., sj, exceto d chaves que se encontram ausentes.

Isto é, T(i,j,d) possui alcance [i+1,j] e exatamente j – i + d chaves do intervalo si+1, ..., sj, sendo aquela de custo mínimo nessas condições.

• A solução do problema é o custo c(0,n,0). Para computar esse valor, calcula-se c(i,j,d) para 0 ≤≤ i ≤≤ ≤≤ j ≤≤ ≤≤≤ n e 0 ≤≤≤≤ d ≤≤≤≤ j – 1. Denota-se por F(i,j,d) a soma das freqüências das chaves reais que compõem T(i,j,d).

• O custo c(i,j,d) pode ser calculado pelas seguintes equações, para

0 ≤≤≤≤ i ≤≤≤≤ j ≤≤≤≤ n:

i

j

d

n

j

i

i

j

d

n

j

i

i

j

d

n

j

i

d

j

i

F

m

d

j

k

c

m

k

i

c

d

j

i

c

d

j

i

c

d

j

i

c

d m j k i

<

<

>

=

+

+

+

=

=

=

+ ≤ ≤≤≤ +

0

0

0

0

)

,

,

(

)}

1

,

,

(

)

,

,

(

{

min

)

,

,

(

)

,

,

(

0

)

,

,

(

1 01

• Para computar a equação acima de forma eficiente, é necessário que os valores de c(i,k,m) e c(k,j,d – m + 1) já sejam conhecidos quando do cálculo de c(i,j,d). Para tanto, basta computar c(i,j,d) em ordem não decrescente de

j – i e, para um valor fixo de j – i, calcular c(i,j,d) em ordem decrescente de m.

• A computação começa com j – i = 0 e d = 0. Em seguida, j – i = 1 e d = 1 e, após, d = 0. Para j – i = 2, computa-se d = 2, em seguida d = 1 e, após, d = 0; e assim por diante.

• Para computar c(i,j,d), é necessário calcular F(i,j,d) que depende de T(i,k,m),

T(k,j,d – m + 1) e do valor da chave real atribuído ao nó raiz de T(i,j,d).

• Para auxiliar o cálculo das freqüências F(i,j,d), são mantidas listas L(i,j,d) das chaves que compõem a subárvore T(i,j,d), em ordem crescente de freqüências, pois a raiz de T(i,j,d) seria a primeira da lista (menor freqüência pelo lema anterior).

• O cálculo de F(i,j,d) seria dado pela expressão a seguir, onde sl é a raiz da

(26)

)

(

)

1

,

,

(

)

,

,

(

)

,

,

(

i

j

d

F

i

k

m

F

k

j

d

m

f

s

l

F

=

+

+

+

(4.1)

• A decomposição da subárvore T(i,j,d) nos vários subproblemas possíveis é apresentada na figura abaixo:

• O algoritmo para obtenção da árvore de binária de busca de partilha ótima encontra-se detalhado a seguir:

 A inicialização consiste em determinar c(i,j,d) para 0 ≤≤ i ≤≤ ≤≤≤ j ≤≤ n e j – i ≤≤ ≤≤≤ d

≤≤

≤ n de acordo com as duas primeiras equações (vistas anteriormente) do cálculo do custo. Além disso, as listas L são inicializadas para esses casos segundo L(i,j,d).

(27)

 Para um dado par de índices i, j, suponha que c(i',j',d'), F(i',j',d') e

L(i',j',d') já tenham sido previamente calculados para todos os valores de i', j', d' satisfazendo a 0 ≤≤≤≤ j’ – i’ ≤≤≤≤ j – i e 0 ≤≤≤≤ d’ ≤≤ j’ – i’ bem como j’ – i’ =

j – i e d' > d. Em particular, quando se considera uma certa subárvore T(i,j,d), supõe-se que as chaves real e de partilha já tenham sido

determinadas para cada nó que compõe T(i,j,d), exceto a raiz. A determinação das chaves real e de partilha da raiz de T(i,j,d) é o objetivo da computação a seguir.

 Para cada valor de d, d < j – i, considera-se a variação de m segundo 0 ≤≤ m ≤ ≤

≤≤

≤ d + 1. Para cada par de valores i e j, 0 ≤≤≤≤ j – i ≤≤≤≤ n, considera-se também a variação do índice k da chave de partilha tentativa, i + 1 ≤≤≤≤ k ≤≤≤≤ j, conforme indicado na terceira equação do cálculo do custo. Para uma certa tentativa de valores de m e k fixos a lista L(i,j,d) se compõe da união das listas

L(i,k,m) e L(k,j,d – m + 1). Para o par de valores m e k considerados, a

chave real sl que seria alocada à raiz de T(i,j,d) é a de menor freqüência

dentre as que compõem L(i,j,d). Todas as listas L devem ser mantidas ordenadas, ao longo do processo.

 Para o par de valores m e k considerados e a chave sl obtida como acima,

determina-se F(i,j,d) de acordo com a equação (4.1).

 Uma vez obtido F(i,j,d), determina-se o valor c(i,j,d) correspondente ao par de valores m e k pela terceira equação do cálculo do custo.

 O valor final de c(i,j,d) será o mínimo dentre todos os experimentados para cada par m e k.

 O valor final de L(i,j,d), bem como o da chave real sl, o de F(i,j,d) e o da

chave de partilha sk alocados à raiz de T(i,j,d) serão os correspondentes ao

par m e k que produziram o custo mínimo.

 A computação termina quando c(0,n,0) é calculado.

• No algoritmo anterior, existem O(n2) pares i, j e O(n3) triplas i, j, d, então o número total de subárvores a serem consideradas, isto é, de subproblemas, é de O(n3). Para computar o custo de cada subárvore T(i,j,d), k e m variam de

acordo com i + 1 ≤≤≤≤ k ≤≤≤≤ j e 0 ≤≤≤≤ m ≤≤≤≤ d + 1, ou seja, um O(n2) computações. As operações da união das listas, bem como a escolha da chave de menor probabilidade em cada lista, podem ser realizadas em tempo inferior a essas. Portanto, a complexidade final do algoritmo é de O(n5).

(28)

• As tabelas a seguir, apresentam um exemplo:

CHAVE FREQÜÊNCIA

s1 10

s2 3

s3 2

• A computação se processa para valores ascendentes de j – i = 0, 1, 2 e 3. Para cada valor de j – i, c(i,j,d) é computado na seqüência decrescente de d = j – i,

j – i – 1, ..., 0. A tabela anterior apresenta os valores de c(i,j,d), F(i,j,d) e L(i,j,d), para uma solução, bem como as chaves de partilha e real

correspondentes. Nos vários casos em que a solução não é única, o algoritmo desempata de forma arbitrária. As subárvores ótimas são mostradas na figura a seguir.

(29)
(30)

B.5 – Árvores Balanceadas

B.5.1 – Conceitos de Balanceamento

• As árvores de buscas binária e de partilha ótimas só podem ser usadas em situações estáticas, pois após algumas inclusões e remoções, elas deixam de ser ótimas.

• A idéia é manter a ordem de grandeza de uma árvore ótima, ou seja,

O(log n), mesmo após algumas inclusões e remoções. Para alcançar essa

finalidade, a estrutura deve ser alterada, periodicamente, de forma a se moldar aos novos dados. O custo dessas alterações, contudo, se mantém em

O(log n). Uma estrutura que opera com essas características é denominada balanceada.

• Na figura a seguir, é apresentado o caso de uma árvore binária que deverá receber um novo nó. Caso o nó seja maior que 14, a árvore continuará completa (caso a), entretanto se o nó for menor que 1, então a altura da árvore resultante será maior do que a anterior (caso b). Logo, para que a árvore mantenha a sua altura, é necessário um reordenamento de todos os seus nós (caso c), através de algumas transformações.

• Para efetuar as transformações nas representações usuais de árvores binárias, é necessário percorrer todos os nós da árvore. Isso implica que o algoritmo de restabelecimento da estrutura requeira, pelo menos, ΩΩΩΩ(n) passos.

• O custo de ΩΩΩ(n) passos para o restabelecimento da árvore é excessivo, Ω mormente considerando-se que operações como inserção ou remoção seriam efetuadas em O(log n) passos. Por esse motivo, as árvores completas (e a busca binária) não são recomendadas para aplicações que requeiram estruturas dinâmicas.

• A idéia é usar uma nova árvore cuja altura seja da ordem de grandeza que a de uma completa com o mesmo número de nós. Isto é, que possua altura igual a O(log n). Além disso, é desejável que esta propriedade se estenda a todas as subárvores: cada subárvore que contém m nós deve possuir altura igual O(log m). Uma árvore que satisfaça essa condição é denominada

(31)

B.5.2 – Árvores AVL

• Uma árvore binária T é denominada AVL quando, para qualquer nó de T, as alturas de suas duas subárvores, esquerda e direita, diferem em módulo de até uma unidade. Nesse caso, υυυυ é um nó regulado. Em contrapartida, um nó que não satisfaça essa condição de altura é denominado desregulado, e uma árvore que contenha um nó nessas condições é também chamada

desregulada.

• Toda árvore completa é AVL, mas não necessariamente vale a recíproca. A figura a seguir mostra uma árvore AVL e outra não AVL (a subárvore esquerda do nó u possui altura 2, enquanto a sua subárvore direita possui altura 0).

(32)

u

Árvore AVL Árvore NÃO AVL

• A seguir será provado que toda árvore AVL é balanceada.

• Em uma árvore binária de altura h, a altura de urna das subárvores da raiz é

h – 1, enquanto a da outra é menor ou igual a h – 1. Numa árvore AVL,

porém, a altura dessa última subárvore se restringe a h – 1 ou h – 2, uma vez que, se fosse menor do que h – 2, sua raiz estaria desregulada. Como se deseja uma árvore AVL com número mínimo de nós, deve-se considerar a segunda subárvore com altura h – 2 e não h – 1.

• É possível agora construir a árvore procurada de forma recursiva. Seja Th

uma árvore AVL com altura h e número mínimo de nós. Para formar Th

consideram-se, inicialmente, os casos triviais. Se h = 0, Th é uma árvore

vazia. Se h = 1, Th consiste em um único nó. Quando h > 1, para formar Th

escolhe-se um nó r como raiz. Em seguida, escolhe-se Th – 1 para formar a

subárvore direita de r, e Th– 2 para a esquerda. Veja as próximas figuras.

• Para h > 1, existem várias árvores AVL com a mesma altura h e o mesmo número mínimo de nós.

(33)

• Seja |Th| o número de nós de Th. Logo:

>

+

+

=

=

=

=

=

− −

1

1

1

1

0

0

2 1

T

para

h

T

T

h

para

T

h

para

T

h h h h h

• Para encontrar o valor de |Th| em termos de h, compara-se |Th| com Fh, o

h-ésimo termo da seqüência de Fibonacci, ou seja:

>

+

=

=

=

=

=

− −

1

1

1

0

0

2 1

F

para

h

F

F

h

para

F

h

para

F

h h h h h

• A árvore Th é chamada de árvore de Fibonacci, justamente pela semelhança

entre |Th| e Fh. Na verdade |Th| ≥≥≥≥ Fh. Para h > 1, Fh é dado por:

1

2

5

1

5

1

1

2

5

1

5

1

,

0

2

5

1

2

5

1

5

1

 −

>

<

 −

>

 −

 +

=

h h h h h h

T

então

h

como

F

(34)

)

(log

5

log

)

1

(

log

log

1

2

5

log

)

1

(

log

1

5

1

2

/

)

5

1

(

2 2

n

O

T

a

h

base

para

passando

h

T

a

T

se

tem

a

fazendo

a h a h a h h

=

+

+

<

>

+

>

+

=

• O resultado acima prova que a árvore AVL é balanceada.

B.5.2.1 – Inclusão em Árvores AVL

• Ao ser feita uma inclusão em uma árvore AVL, é possível que a mesma deixe de ser uma árvore AVL, então é necessário proceder com transformações para que a árvore resultante após a inclusão permaneça AVL. • A idéia consiste em verificar, após cada inclusão, se algum nó p se encontra

desregulado. Em caso positivo, algumas transformações devem ser aplicadas para regular o nó. As figuras a seguir mostram as transformações a serem utilizadas.

(35)

• A figura a seguir mostra uma árvore com a raiz e sofrendo uma rotação direita.

• Se um nó q foi incluído em T, e, após a inclusão, todos os nós mantiveram-se regulados, então a árvore manteve-se AVL e não há nada a fazer. Caso contrário, seja p o nó mais próximo às folhas de T que se tornou desregulado. O nó p se encontra no caminho de q à raiz de T e sua escolha é única. Sejam

hE(p) e hD(p) as alturas das subárvores esquerda e direita de p,

respectivamente. Naturalmente, |hE(p) – hD(p)| = 2, pois T era uma árvore

AVL e, além disso, a inclusão de um nó não pode aumentar em mais de uma unidade a altura de qualquer subárvore. Podem-se identificar os seguintes casos de análise:

 Caso 1: hE(p) > hD(p)

O nó q pertence à subárvore esquerda de p. Além disso, p possui o filho esquerdo u ≠≠≠≠ q. Pois, caso contrário, p não estaria desregulado. Finalmente, por esse mesmo motivo, sabe-se que hE(u) ≠≠≠≠ hD(u). As duas

seguintes possibilidades podem ocorrer:

(36)

Essa situação corresponde ao caso (a) da figura anterior (das transformações), sendo q um nó pertencente a T1. Observe que h(T1) -

h(T2) = 1 e h(T2) = h(T3). Conseqüentemente, a aplicação da rotação

direita da raiz p restabelece a sua regulagem.

 Caso 1.2: hD(u) > hE(u)

Então u possui o filho direito υυυυ (caso (e) da figura das transformações). Nesse caso, vale

)

(

)

(

)}

(

),

(

max{

1

)

(

)

(

T

2

h

T

3

e

h

T

2

h

T

3

h

T

1

h

T

4

h

=

=

(37)

Aplica-se então a rotação dupla direita da raiz p.  Caso 2: hD(p) > hE(p)

Dessa vez, p possui o filho direito z ≠≠≠≠ q. Segue-se que hE(z) ≠≠≠≠ hD(z) e as

demais situações são as seguintes:

 Caso 2.1: hD(z) > hE(z)

Nesse caso (caso (c) da figura das transformações), q pertencente a T3.

As relações de altura são agora

)

(

)

(

1

)

(

)

(

T

3

h

T

2

e

h

T

2

h

T

1

h

=

=

Isso significa que a rotação esquerda torna a árvore novamente AVL.

 Caso 2.2: hE(z) > hD(z)

Então z possui o filho esquerdo y (caso (g) da figura das transformações). As alturas das subárvores T1, T2, T3 e T4 satisfazem as

mesmas relações do caso 1.2. Dessa forma, a aplicação da rotação dupla esquerda transforma a subárvore na indicada no caso (h) da figura das transformações e p torna-se novamente regulado.

• O processo completo de inclusão pode ser dividido nos seguintes passos: (1) Inicialmente, efetua-se uma busca em T para verificar se x (chave a ser

inserida) já se encontra em T. Em caso positivo, o processo deve ser encerrado. Caso contrário, a busca encontra o local correto do novo nó. (2) Faz-se, então, a inclusão propriamente dita.

(3) Deve-se verificar, em seguida, se essa operação tornou algum nó desregulado. Em caso negativo, o processo termina, pois T permanece AVL. Caso contrário, a regulagem de T deve ser efetuada.

(4) A regulagem consiste na execução de uma das operações de rotação da figura das transformações. Procede-se com o reconhecimento da operação de rotação indicada ao caso e, então, faz-se a sua execução.

(38)

• Para verificar se algum nó υυυυ de T se tornou desregulado após a inclusão, basta determinar as alturas de suas duas subárvores e subtrair uma da outra. Essa operação pode ser realizada, sem dificuldades, percorrendo-se a subárvore de raiz υυυ em T. Isso, entretanto, consome tempo O(n). Além disso υ qualquer nó do caminho de q até a raiz da árvore pode ter se tornado desregulado. Então, a aplicação direta desse procedimento conduz a um algoritmo de complexidade O(n logn).

• A complexidade anterior pode ser reduzida para O(n), percorrendo-se T de baixo para cima e armazenando-se o valor da altura de cada subárvore. Entretanto, para alcançar a meta de efetuar a inclusão em tempo O(logn), utiliza-se um processo diferente.

• Define-se o rótulo balanço(υυυ), para cada nó υυ υυ de T como: υ

balanço(

υ

) = h

D

(

υ

) – h

E

(

υ

)

• O nó υυυ está regulado se e somente se –1 ≤υ ≤≤≤ balanço(υυυυ) ≤≤≤≤ 1.

• Se q pertencer à subárvore esquerda de υυυ e essa inclusão ocasionar um υ aumento na altura dessa subárvore então subtrai-se uma unidade de

balanço(υυυ). Se esse valor diminuir para –2, o nó υυ υυ estará desregulado. υ Analogamente, se q pertencer à subárvore direita de υυυ e provocar um υ aumento de sua altura, adiciona-se uma unidade a balanço(υυυυ). O nó υυυυ ficará desregulado, nesse caso, se balanço(υυυ) aumentar para 2. υ

• Para completar o processo, resta ainda determinar em que casos a inclusão de

q provoca um acréscimo na altura h(υυυ) da subárvore T. A inserção do nó q υ acarreta obrigatoriamente uma alteração na altura da subárvore esquerda ou direita (de acordo com a nova chave) de seu nó pai ωωω. A situação do campo ω

balanço permite avaliar se essa alteração pode, ou não, se propagar aos

outros nós υυυ do caminho de ωυ ωωω até a raiz da árvore.

• Supondo que o nó q é inserido na subárvore esquerda de υυυυ. A análise se inicia com υυυ = ωυ ωωω e prossegue em seus nós ancestrais, de forma recursiva. O processo se encerra quando da constatação de que a altura de T não foi modificada, ou de que υυυ se tornou regulado. Três casos distintos podem υ ocorrer:

Referências

Documentos relacionados

Figura A53 - Produção e consumo de resinas termoplásticas 2000 - 2009 Fonte: Perfil da Indústria de Transformação de Material Plástico - Edição de 2009.. A Figura A54 exibe

Depois da abordagem teórica e empírica à problemática da utilização das Novas Tecnologias de Informação e Comunicação em contexto de sala de aula, pelos professores do

Visto que no Brasil esta aplicação em grande escala é praticamente nula, torna-se um atrativo acadêmico o estudo dos impactos da inserção de unidades

Criar um modelo de Política Nutricional e Alimentar em contexto escolar e desenvolver o Plano Estratégico de Intervenção Piloto (PEIP) que daí advém, em particular em

Neste tipo de situações, os valores da propriedade cuisine da classe Restaurant deixam de ser apenas “valores” sem semântica a apresentar (possivelmente) numa caixa

O presente relatório de estágio foi realizado no âmbito da “Dissertação”, inserida no plano curricular do Mestrado Integrado em Engenharia Civil. O relatório agora

individual que habilita as pessoas para a competição pelos empregos disponíveis” (SAVIANI, 2013, p.. se deparado com tentativas de controle que advêm não só do Estado, mas também

Purpose: This thesis aims to describe dietary salt intake and to examine potential factors that could help to reduce salt intake. Thus aims to contribute to