• Nenhum resultado encontrado

CHAPTER ONE. Representações

N/A
N/A
Protected

Academic year: 2021

Share "CHAPTER ONE. Representações"

Copied!
36
0
0

Texto

(1)

CHAPTER ONE

Árvores

Estruturas lineares, como vetores e listas não são adequadas para

representar dados que devem ser dispostos de forma hierárquica. Árvores são as estruturas adequadas para isso.

Uma árvore é formada por um conjunto finito T de elementos denominados vértices ou nós de tal modo que se T = 0 a árvore é vazia, caso contrário temos um nó especial chamado raiz da árvore (R), e cujos elementos restantes são particionados em m >= 1 conjuntos distintos não vazios, as subárvores de R, sendo cada um destes conjuntos por sua vez uma árvore.

Representações

Há diversas maneiras de representar árvores. Tradicionalmente representa-se as estruturas em árvores com a raíz para cima e as folhas para baixo.

nó raíz

sub-árvores

Uma representação que reflete a idéia de árvores como conjuntos aninhados é mostrado na figura abaixo.

(2)

Uma outra forma de representar uma árvore é a representação por

parênteses aninhados. A seqüência de parênteses representa a relação entre os nós da estrutura. O rótulo do nó é inserido à esquerda do abre parênteses correspondente. Esta representação tem importância, por exemplo, no

tratamento de expressões aritméticas, já que toda expressão aritmética pode ser colocada nesta forma. Se colocarmos uma expressão nesta forma podemos então representá-la como uma árvore, mostrando como ela seria calculada. + A * E F - % * C B D (A + ((B - C) * (D % (E * F))))

Terminologia

Uma árvore é composta por um conjunto de nós. Há um nó R, o nó raiz, que contém zero ou mais sub-árvores, cujas raízes são ligadas diretamente a R. Esses nós raízes das sub-árvores são os filhos do nó pai, R.

Os conjuntos das subárvores tem de ser disjuntos. Logo, a estrutura indicada na figura a seguir não é uma árvore.

(3)

A B C E F H I J J Pai

As raízes das subárvores de um nó X são os filhos de X. X é o pai dos filhos. Irmão

Os filhos (descendentes) de um mesmo nó pai (antecessor) são denominados irmãos.

Aresta

Uma aresta (arco) liga um nó a outro. Floresta

Uma floresta é um conjunto de zero ou mais árvores. Folha

Nós que não possuem filhos (ou descendentes) são chamados folhas, nós terminais ou nós externos. A B C D E F G H I J K L Não-folha

Nós com filhos (ou descendentes) são chamados nós internos, não-folhas ou não terminais.

(4)

B C D

E F G H I J

K L

Grau de um nó

O número de descendentes (imediatos) de um nó é denominado grau deste nó. O grau de uma folha é zero.

A B C D E F G H I J K L Grau A 3 B 2 C 1 D 3 E 1 F 0 G 0 H 1 I 0 J 0 K 0 L 0 Caminho

Uma seqüência de nós distintos v1, v2, ..., vk tal que sempre exista a relação “vi é filho de vi+1” ou “vi é pai de vi + 1”, 1 ≤ i < k, é denominada um

caminho entre v1 e vk.

Diz-se que v1 alcança vk ou que vk é alcançado por v1.

Um caminho de k vértices v1, v2, ..., vk é formado pela seqüência de k-1 pares de nós (v1, v2), (v2, v3), ..., (vk - 2, vk - 1), (vk - 1, vk).

• k-1 é o comprimento do caminho

• Cada par (vi, vi + 1) é uma aresta ou arco, 1 ≤ i < k. No exemplo ilustrado a seguir:

• A, D, H, L é um caminho entre A e L, formando pela seqüência de arestas (A,D), (D,H), (H,L);

(5)

A

B C D

E F G H I J

K L

Nível

O nível (ou profundidade) de um nó é definido admitindo-se que a raiz está no nível zero (nível um). Estando um nó no nível i, seus filhos estarão no nível i + 1. Não existe um padrão quanto ao nível adotado para a raiz, que determina o nível dos demais nós. Assim, a raiz pode ser admitida como estando no nível zero.

No exemplo a seguir, os nós: • B, C e D estão no nível 1; • K e L estão no nível 3. A B C D E F G H I J K L 0 1 2 3 Altura de um nó

A altura de um nó é o número de arestas no maior caminho desde o nó até um de seus descendentes. Portanto, as folhas têm altura zero. No exemplo a seguir, os nós:

• K, F, G, L, I, J têm altura 0 • E, C e H têm altura 1 • B e D têm altura 2 • A tem altura 3

(6)

B C D E F G H I J K L 1 1 1 2 2 3

Altura de uma árvore

A altura (ou profundidade) de uma árvore é o nível máximo entre todos os nós da árvore ou, equivalentemente, é a altura da raiz. Na imagem anterior, a árvore possui altura 3.

Grau de uma árvore

O grau máximo atingido pelos nós de uma árvore é denominado grau desta árvore. No imagem anterior, o grau da árvore é 3.

Árvore completa

Uma árvore de grau d é uma árvore completa (cheia) se:

• Todos os nós tem exatamente d filhos, exceto as folhas e; • Todas as folhas estão na mesma altura

No exemplo ilustrado a seguir, a árvore de grau d=3 é completa.

A

B C D

E F G H I J K L M

Árvores balanceadas

• Uma árvore é balanceada se, para cada nó, a altura de suas subárvores diferem, no máximo, de uma unidade.

• Uma árvore é perfeitamente balanceada se, para cada nó, os números de nós em suas subárvores diferem, no máximo, de uma unidade.

(7)

balanceadas.

Exemplo de árvore balanceada de grau 2:

Árvore orientada

Uma árvore orientada (ordenada) é uma árvore na qual os filhos de cada nó são orientados (ordenados). A orientação é da esquerda para a direita

Implementação

Árvores podem ser implementadas utilizando listas ligadas.

Cada nó possui um campo de informação e uma série de campos de ligação, de acordo como número de filhos daquele nó. Entretanto, é mais simples o caso em que cada nó tem um número máximo de filhos d pré-estabelecido. Exemplo de representação de uma árvore ternária:

(8)

CHAPTER TWO

Árvores binárias

Árvores binárias são árvores orientadas de grau 2. Em uma árvore binária, cada nó tem zero, um ou dois filhos (subárvores). Quando há somente uma subárvore presente, é necessário distinguir entre subárvore esquerda e direita.

Formalmente, uma árvore binária pode ser definida como um conjunto finito de nós, que é vazio, ou consiste de um nó raiz e dois conjuntos disjuntos de nós, a subárvore esquerda e a subárvore direita. É importante observar que uma árvore binária não é um caso especial de árvore e sim um conceito completamente diferente. nó raíz sa e sa d vazia

Por exemplo, na figura abaixo há duas árvores idênticas, mas são duas

árvores binárias diferentes. Isto porque uma delas tem a subárvore da direita vazia e a outra a subárvore da esquerda.

(9)

a b c d a b d c

A figura a seguir ilustra um exemplo de árvore binária. A árvore é composta pelo nó a, pela subárvore à esquerda formada por b e d, e pela sub-árvore à direita formada por c, e e f. O nó a representa a raiz da árvore, e os nós b e c, as raízes das sub-árvores. Os nós d, e e f são folhas da árvore. Cada nó folha também representa uma uma árvore, com as sub-árvores vazias.

a

b c

d e f

Numa representação textual, a árvore vazia é representada por <>, e árvores não vazias, por <raiz sae sad>. A árvore acima seria representada por:

<a<b<><d<><>>><c<e<><>><f<><>>>>.

B

A

B A

As duas árvores binárias ao lado são distintas: • a primeira tem subárvore direita vazia • a segunda tem subárvore esquerda vazia Percurso em uma árvore binária

Os nós de uma árvore binária podem ser visitados de três formas (varredura da árvore):

(10)

R

E D

• Pré-ordem (pre-order): R, E, D. Visitar a raiz antes das sub-árvores;

• Em-ordem (in-order): E, R, D. Visitar a sub-árvore esquerda, a raiz e a sub-árvore direita;

• Pós-ordem (post-order): E, D, R. Visitar a raiz após visitar as sub-árvores Exemplo: * + -a / d * f c b e • Pré-ordem: • *+a/bc–d*ef • Em-ordem: • a+b/c*d–e*f • Pós-Ordem: • abc/+def*–*

Implementação

(11)

árvore possui uma raiz (root), uma árvore vazia pode ser representada por um ponteiro nulo (NULL)

Cada nó em uma árvore binária possui um campo de dados, um ponteiro para a sub-árvore esquerda e um ponteiro para a sub-árvore direita. É possível definir um tipo para essa representação.

A estrutura de dados utilizada deve armazenar três informações: a

informação propriamente dita, um ponteiro para a sub-árvore à direita e um ponteiro para a sub-árvore à esquerda.

Esquematicamente:

(12)

Exemplo

Exemplo: implementar uma solução para armazenar os termos de uma operação matemática em uma árvore binária.

arvore.h //

// arvore.h

// definição da classe e suas interfaces // #ifndef ARVOREHEADER #define ARVOREHEADER class ArvoreBinaria { private: // declaração de tipos struct No {

char valor; // tipo de dado colocado na árvore No *noEsquerdo, *noDireito; // subárvores

};

typedef No *PonteiroRaiz; // declaração de campos PonteiroRaiz raiz;

// declaração de métodos

int numeroNos(PonteiroRaiz &r); int altura(PonteiroRaiz &t);

int numeroFolhas(PonteiroRaiz &t); int noInterno();

(13)

void percursoPreOrdem(PonteiroRaiz &t); void percursoPosOrdem(PonteiroRaiz &t); void percursoSimOrdem(PonteiroRaiz &t); void processa(char);

void imprimir(PonteiroRaiz &t, int s); PonteiroRaiz obterRaiz() { return raiz; } public: ArvoreBinaria(); ArvoreBinaria(int x);

void atribuirArvoreEsquerda(ArvoreBinaria &t); void atribuirArvoreDireita(ArvoreBinaria &t); int numeroNos(); int altura(); int numeroFolhas(); void percursoPreOrdem(); void percursoPosOrdem(); void percursoSimOrdem(); void imprimir(); }; #endif arvore.cpp // // arvore.cpp

(14)

// #include <iostream> #include <iomanip> #include "arvore.h" using namespace std; // declaração de campos ArvoreBinaria::ArvoreBinaria() { raiz = (PonteiroRaiz)malloc(sizeof(No)); } ArvoreBinaria::ArvoreBinaria(int x) { raiz = (PonteiroRaiz)malloc(sizeof(No)); raiz->valor = x; } // Geral

void ArvoreBinaria::atribuirArvoreEsquerda(ArvoreBinaria &t) { raiz->noEsquerdo = t.obterRaiz();

}

void ArvoreBinaria::atribuirArvoreDireita(ArvoreBinaria &t) { raiz->noDireito = t.obterRaiz();

}

int ArvoreBinaria::noInterno() {

return (raiz != NULL && (raiz->noDireito != NULL || raiz->noEsquerdo != NULL));

(15)

// Número de nós ---int ArvoreBinaria::numeroNos() {

return numeroNos(raiz); }

int ArvoreBinaria::numeroNos(PonteiroRaiz &r) { if(r == NULL)

return 0; else

return 1 + >noEsquerdo) + numeroNos(r->noDireito); } //Altura ---int ArvoreBinaria::altura() { return altura(raiz); }

int ArvoreBinaria::altura(PonteiroRaiz &t) { if(t == NULL) return -1; else { int esquerdo,direito; esquerdo = altura(t->noEsquerdo); direito = altura(t->noDireito); if(esquerdo > direito) return esquerdo + 1; else

(16)

} } // Número de folhas ---int ArvoreBinaria::numeroFolhas() { return numeroFolhas(raiz); }

int ArvoreBinaria::numeroFolhas(PonteiroRaiz &t) { if(t == NULL)

return 0;

else if(t->noEsquerdo == NULL && t->noDireito == NULL) return 1; else

return >noEsquerdo) + numeroFolhas(t->noDireito);

}

// Percurso em pré-ordem

---void ArvoreBinaria::percursoPreOrdem() {

cout << "Percurso em pré-ordem" << endl; percursoPreOrdem(raiz);

}

void ArvoreBinaria::percursoPreOrdem(PonteiroRaiz &t) { if(t != NULL) {

processa(t->valor);

(17)

percursoPreOrdem(t->noDireito); }

}

// Percurso em ordem simétrica

---void ArvoreBinaria::percursoSimOrdem() {

cout << "Percurso em ordem simétrica" << endl; percursoSimOrdem(raiz);

}

void ArvoreBinaria::percursoSimOrdem(PonteiroRaiz &t) { if (t == NULL) return; if (noInterno()) percursoSimOrdem(t->noEsquerdo); processa(t->valor); if (noInterno()) percursoSimOrdem(t->noDireito); } // Percurso em pós-ordem ---void ArvoreBinaria::percursoPosOrdem() {

cout << "Percurso em pós-ordem" << endl; percursoPosOrdem(raiz);

(18)

if(t != NULL) { percursoPosOrdem(t->noEsquerdo); percursoPosOrdem(t->noDireito); processa(t->valor); } } // Impressão ---void ArvoreBinaria::imprimir() { ArvoreBinaria::imprimir(raiz, 0); }

void ArvoreBinaria::imprimir(PonteiroRaiz &t, int s) { int i;

if(t != NULL) {

imprimir(t->noEsquerdo, s + 3); for(i = 1; i <= s; i++)

cout << " "; // espaços

cout << setw(6) << t->valor << endl; imprimir(t->noDireito, s + 3);

} }

void ArvoreBinaria::processa(char i) {

cout << "Valor: [" << i << "]" << endl; }

(19)

main.cp // // main.cpp // rotina de teste // #include <iostream> #include "arvore.h" using namespace std; // Main ---int main () { ArvoreBinaria r1('*'); ArvoreBinaria r2('+'); ArvoreBinaria r3('-'); ArvoreBinaria r4('a'); ArvoreBinaria r5('/'); ArvoreBinaria r6('d'); ArvoreBinaria r7('*'); ArvoreBinaria r8('b'); ArvoreBinaria r9('c'); ArvoreBinaria r10('e'); ArvoreBinaria r11('f'); r1.atribuirArvoreEsquerda(r2);

(20)

r2.atribuirArvoreEsquerda(r4); r2.atribuirArvoreDireita(r5); r3.atribuirArvoreEsquerda(r6); r3.atribuirArvoreDireita(r7); r5.atribuirArvoreEsquerda(r8); r5.atribuirArvoreDireita(r9); r7.atribuirArvoreEsquerda(r10); r7.atribuirArvoreDireita(r11); r1.imprimir();

cout << "Nós : " << r1.numeroNos() << endl; cout << "Folhas: " << r1.numeroFolhas() << endl; cout << "Altura: " << r1.altura() << endl;

r1.percursoPreOrdem(); r1.percursoPosOrdem(); r1.percursoSimOrdem(); return 0;

(21)

CHAPTER THREE

B-Tree

Uma B-tree, ou bárvore, é uma árvore de busca equilibrada, desenhada para ter um bom desempenho em dispositivos de memória secundária de acesso direto pois elas minimizam o número de operações de movimentação de dados (escrita/leitura) numa pesquisa ou alteração. Não confundir com árvore binária. 134 24 70 3 8 21 26 61 81 88 131 140 150 160 215 244 163 200 216 219 239 250 298

Uma árvore B de ordem "m" (máximo de filhos para cada nó) é uma árvore que atende as seguintes propriedades:

• Cada nó tem no máximo "m" filhos

• Cada nó (exceto a raíz e as folhas) tem pelo menos "m/2" filhos • A raiz tem uma chave e pelo menos dois filhos, se ela mesma não for uma folha

• Todas as folhas aparecem no mesmo nível (balanceamento) e carregam informação

• Um nó não-folha com "k" filhos deve ter k-1 chaves Vantagens na utilização:

• Melhor desempenho por ter um número menor de nós do que uma árvore binária, por exemplo. Menos nós, significa menos altura que resulta em menos acessos ao disco.

• Por garantir poucos ponteiros entre os nós, há uma economia de espaço.

(22)

balanceamento da árvore, a cada inclusão/exclusão.

• Permite um tempo de acesso de dados menor, em uma busca aleatória, por causa de suas ramificações.

Estrutura do nó

Nós em árvores B, também denominado páginas, geralmente são

representados por um conjunto de elementos apontando para seus filhos, estes que por sua vez também podem ser conhecidos como folhas. Alguns autores consideram a ordem de uma árvore B como sendo a quantidade de registros que a página pode suportar. Outros consideram a ordem como a quantidade de campos apontadores. Todo nó da árvore tem um número mínimo de elementos, que é dado pela metade do valor da ordem do nó, truncando o número caso a árvore seja de ordem ímpar, exceto para a raiz da árvore, que pode ter o mínimo de um registro. Por exemplo, os nós de uma árvore de ordem 5, devem ter, no mínimo 5 / 2 = 2,5 registros, ou seja, dois registros. A quantidade de filhos que um nó pode ter é sempre a

quantidade de registros do nó mais 1 (V+1). Por exemplo, se um nó tem 4 registros, este nó terá obrigatoriamente 5 apontamentos para os nós filhos. Numa bárvore, cada nó tem vários valores e vários filhos. Os nós internos que tenham n valores têm n + 1 filhos.

O número mínimo de filhos num nó interno de uma árvore é o grau mínimo da árvore. Se grau mínimo for t cada nó tem pelo menos t-1 valores. Numa bárvore de grau t, o número máximo de valores num nó é 2t-1 e, portanto, o número máximo de filhos é 2t.

Os valores num nó estão ordenados e separam os valores das subárvores correspondentes.

Exemplo de estrutura em C: #define M 5 //ordem da arvore struct No{

int n;

char chave[M-1]; struct No *pProx[M];

(23)

};

Implementação

Ao pôr e ao remover, é preciso garantir que as propriedades da bárvore se mantenham.

Para inserir ou remover variáveis de um nó, a quantidade de descendentes no nó não poderá ultrapassar sua ordem e nem ser menor que sua ordem dividida por dois (fator de preenchimento). Árvores B têm vantagens

substanciais em relação a outros tipos de implementações quanto ao tempo de acesso e pesquisa aos nós e não presisam ser rebalanceadas como são freqüentemente as árvores de busca.

Pesquisa em Árvores B

• Primeiro, o bloco contendo a raiz é lido.

• O algoritmo de pesquisa então inicia a verificação de cada um dos registros (se ele não estiver cheio, tantos quantos o nó atualmente

armazena) iniciando pelo registro 0.

• Quando ele encontra um registro com chave maior, ele sabe que deve ir para o filho que reside entre este registro e o precedente.

• Este processo continua até o nó correto ser encontrado.

• Se uma folha é alcançada sem encontrar a chave específica, a pesquisa não obteve sucesso.

Inserção em Árvores B

Em uma árvore B é importante manter um nó tão cheio quanto possível. Deste modo cada acesso à disco, o qual lê uma entrada de um nó, pode adquirir o máximo de quantidade de dados.

CHAPTER FOUR

Árvore B+

Uma árvore B+ é uma variação de bárvores. Numa árvore-B+, contrastando com uma árvore-B, todos os dados são gravados nas folhas. Os nós internos contêm apenas chaves e apontadores da árvore. Todas as folhas estão no mesmo nível mais baixo. Os nós das folhas também estão ligados entre si

(24)

Na figura abaixo, um exemplo de uma árvore B+ que liga as chaves 1-7 aos valores de dados d1-d7. Notar a lista ligada.

3 5

1 2 3 4 5 6 7

d1 d2 d3 d4 d5 d6 d7

O número máximo de apontadores num registo é chamado de ordem da árvore B+.

O número mínimo de chaves por registo é metade do número máximo de chaves. Por exemplo, se a ordem de uma árvore B+ for n+1, cada nodo (excepto o da raiz) terá de ter entre (n+1)/2 e n chaves. Se n for um número primo, o número mínimo de chaves pode ser (n+1)/2 ou (n-1)/2, mas terá de ser o mesmo em toda a árvore.

O número de chaves que poderá ser indexado ao usar a árvore B+ é uma função da ordem da árvore e da sua altura.

Utilização

O problema fundamental com a manutenção de um índice em disco é que o acesso é muito lento.

O melhor acesso a um índice ordenado, até agora, foi dado pela Pesquisa Binária, porém:

Pesquisa binária requer muitos acessos. 15 ítens podem requerer 4 acessos. 1000 ítens requerem, em média, 9.5 acessos.

Pode ser muito caro manter um índice ordenado. É necessário um método no qual a inserção e a eliminação de registros tenha apenas efeitos locais, isto é, não exija a reorganização total do índice.

(25)

Árvore Binária de Busca: os registros são mantidos num arquivo, e ponteiros indicam onde estão os registros filhos (esq e dir).

Vantagem: a ordem dos registros não está associada à ordem no arquivo físico.

Desvantagem: o arquivo físico não precisa estar ordenado.

É necessário apenas saber onde inserir. A busca pelo registro é necessária, mas a reorganização do arquivo não é.

Solução por AVL Trees

A eficiência do uso de árvores binárias de busca exigem que estas sejam mantidas balanceadas. Isso implica o uso de árvore AVL, e dos algoritmos associados para inserção de eliminação de registros.

O uso dessas árvores AVL, entretanto, não supera as vantagens da

organização da árvores em páginas (abaixo). A combinação das técnicas não é interessante por que a necessidade de balancear a árvore causaria

modificação das páginas!

Solução por Paged Binary Trees

O velho problema está de volta: a busca por uma posição é muito lenta. Novamente temos o caso de que, uma vez encontrada a posição, podemos ler uma grande quantidade registros sequencialmente.

Isso nos leva à noção de página: uma vez localizada, todos os registros são lidos.

(26)

A divisão de uma árvore binária em páginas é ilustrada na figura acima. Nessa árvore de 9 páginas, quaisquer dos 63 registros podem ser acessados em no máximo 2 acessos.

Se na figura cada página tiver 8kbytes e contiver 511 chaves; a árvore cheia e completamente balanceada, nas quais cada página é também uma árvore completamente balanceada: apresentará um total de 134.217.727 chaves que podem ser acessadas em no máximo 3 acessos ao disco.

Pior caso para:

árvore binária: log2 (N+1)

versão em páginas: logk+1 (N+1)

Onde k é o número de chaves de uma página; note que, para árvores binárias, k=2.

Veja o efeito da escala logarítmica quando k = 2 e k = 511: • árvore binária: log2 (134.217.727) = 27 acessos

• versão em páginas: log511+1 (134.217.727) = 3 acessos Preços a pagar:

• tempo na transmissão de grande quantidade de dados • manutenção da ordem da árvore.

CHAPTER FIVE

Árvore binária de busca

Árvores binárias de busca, ou binary search trees, BST, são árvores binárias onde, para cada sub-árvore, todos os elementos da sub-árvore da esquerda são menores do que a raiz e a raiz é menor do que todos os elementos da subárvore da direita. Sendo assim, ao fazer um percurso direto da árvore,

(27)

apanhamos os elementos por ordem crescente: 3, 12, 14, 32, 42, 43, 44, 47, 49, 51, 58, 63.

Para localizar um elemento, comparamos seu valor. Se o elemento procurado é igual à raiz, elemento encontrado; se não, se é menor, procuramos na sub árvore esquerda; se não, procuramos na subárvore direita. A figura ao lado mostra o caminho percorrido para localizar o nó com o valor 49.

Podemos utilizar uma árvore de busca para a busca de registros

armazenados em um arquivo em disco. Os valores na árvore podem ser um dos campos do arquivo, chamado campo de busca (por isso árvores binárias de busca são também conhecidas como dicionários binários, um campo chave (key) é um dado de identifica univocamente uma informação). Cada valor de chave na árvore é associado a um ponteiro para o registro no arquivo de dados que possua aquele valor.

Na pesquisa binária, as operações de busca em um conjunto com n

elementos podem ser efetuadas em O(log2n), na realidade, ⎡log2 n⎤ mesmo no pior caso. No caso em que o conjunto de elementos se encontre em um arquivo em disco, sendo que os elementos (registros) estão ordenados por um campo chave então seria possível aplicar uma pesquisa binária para encontrar um elemento chave qualquer.

Inserir um nó

Para adicionar um elemento x a uma árvore de busca basta localizar a posição correta:

• Se a árvore estiver vazia, adicione um novo nó contendo o elemento x

• Se a raiz é maior que x então insira x na subárvore esquerda, caso contrário insira x na subárvore direita

(28)

Para remover um elemento x a uma ABB há três situações possíveis: • Não existe nenhum nó com chave igual a x

• O nó com chave x possui, no máximo, uma das subárvores • O nó com chave x possui as duas subárvores

Apagar uma folha não representa problema. Apagar um nó que possui um filho também é simples: liga-se o (único) filho do nó apagado ao avô, do mesmo lado.

Para apagar um nó com dois filhos, troca-se o valor do nó com o valor do nó seguinte e depois apaga-se o nó seguinte. O nó seguinte de um nó com dois filhos ou é uma folha ou só tem um filho. Logo, pode apagar-se como

descrito anteriormente.

Exemplo

arvore.h #ifndef ARVORE_H_INCLUDED #define ARVORE_H_INCLUDED class BinarySearchTree { private: // declaracao de tipos

(29)

struct TreeNode {

// tipo de dado colocado na arvore

int valor;

// subarvores

TreeNode *noEsquerdo, *noDireito; };

typedef TreeNode *TreePointer;

// declaracao de campos TreePointer root;

void excluir(int x, TreePointer &t);

void excluirMenor(TreePointer &q, TreePointer &r); bool procuraRecursiva(int x, TreePointer &t);

void imprimir(TreePointer &q, int level); public:

BinarySearchTree(); ~BinarySearchTree(); void inserir(int x); void excluir(int x); bool procurar(int x); void limpar() {}; void imprimir(); bool vazio(); bool cheio(); int minimo(); int maximo();

(30)

#endif // ARVORE_H_INCLUDED arvore.cpp #include <iostream> #include <cstdlib> #include <iomanip> #include "arvore.h" using namespace std; // construtor BinarySearchTree::BinarySearchTree() { root = NULL; } // destrutor BinarySearchTree::~BinarySearchTree() { limpar(); }

// indicar se a arvore esta vazia

bool BinarySearchTree::vazio() { return (root == NULL);

}

// indicar se a arvore esta cheia

bool BinarySearchTree::cheio() {

returnfalse;

(31)

// retornar o menor elemento

int BinarySearchTree::minimo() { TreePointer t=root;

if(t == NULL) {

cout << "Arvore vazia" << endl; exit(0);

}

while (t->noEsquerdo != NULL)

t = t->noEsquerdo; // procurar subarvore esquerda return t->valor;

}

// retornar o maior elemento

int BinarySearchTree::maximo() { TreePointer t=root;

if(t == NULL) { cout << "Arvore vazia" << endl;

exit(1); }

while (t->noDireito != NULL)

t = t->noDireito; // procurar subarvore direita return t->valor;

}

// procurar um elemento

bool BinarySearchTree::procurar(int x) { TreePointer t=root;

(32)

t = t->noEsquerdo; // procurar subarvore esquerda

else

t = t->noDireito; // procurar subarvore direita

return (t != NULL); }

// inserir um item

void BinarySearchTree::inserir(int x) { TreePointer p=NULL, q=root, r; while (q != NULL) { p = q; if(x < q->valor) q = q->noEsquerdo; else q = q->noDireito; } r = new TreeNode; r->valor = x; r->noEsquerdo = NULL; r->noDireito = NULL; if(p == NULL)

root = r; // arvore vazia

else if(x < p->valor) p->noEsquerdo = r;

(33)

else

p->noDireito = r; }

// excluir um item

void BinarySearchTree::excluir(int x) { excluir(x,root); } // imprimir o conteudo void BinarySearchTree::imprimir() { imprimir(root, 0); } // imprimir o conteudo

void BinarySearchTree::imprimir(TreePointer &t, int level) {

int i;

if(t != NULL) {

imprimir(t->noEsquerdo, level + 3);

for(i = 1; i <= level; i++) cout << " "; // espacos

cout << setw(6) << t->valor << endl; imprimir(t->noDireito, level + 3); }

}

// excluir um item

void BinarySearchTree::excluir(int x, TreePointer &p) { TreePointer q;

(34)

cout << "Elemento inexistente"; abort(); } if(x < p->valor) excluir(x,p->noEsquerdo); else if(x > p->valor) excluir(x,p->noDireito); else { // remover p-> q = p; if(q->noDireito == NULL) p = q->noEsquerdo; else if(q->noEsquerdo == NULL) p = q->noDireito; else excluirMenor(q, q->noDireito); delete q; } } // excluir um item

void BinarySearchTree::excluirMenor(TreePointer &q, TreePointer &r) {

(35)

if(r->noEsquerdo != NULL) excluirMenor(q,r->noEsquerdo); else { q->valor = r->valor; q = r; r = r->noDireito; } } main.cpp #include <iostream> #include "arvore.h" using namespace std; int main() { BinarySearchTree tree; tree.inserir(10); tree.inserir(2); tree.inserir(3); tree.inserir(11); tree.inserir(4); tree.inserir(1); tree.inserir(15); tree.inserir(5); tree.excluir(11); tree.inserir(9); tree.excluir(4); tree.inserir(14);

(36)

if (tree.procurar(5))

cout << "Encontrou" << endl; else

cout << "Nao encontrou" << endl;

cout << "Menor elemento: " << tree.minimo() << endl; cout << "Maior elemento: " << tree.maximo() << endl;

return0;

}

Árvores ordenadas

Uma árvore orientada (ordenada) é uma árvore na qual os filhos de cada nó são orientados (ordenados).

Referências

Documentos relacionados

Estudo do Meio (Descoberta das inter-relações entre espaços: o contacto entre a terra e o mar.. Exemplo de abordagem Articulação com conteúdos

res de Tridax procumbens submetidos a diferentes concentrações de 2,4-D, AIB ou ANA acrescidos de BAP.. FIGURA 5 – Peso da matéria seca dos calos formados a partir de segmentos

- Uma boa condição de equilíbrio consiste em definir uma árvore equilibrada em altura como sendo uma árvore em que a diferença das alturas entre as subárvores esquerda

Puxe a alça lateral segurando pela fivela que você alcançou trazendo-a para baixo e cruzando suas costas até encontrar a outra parte da fivela que está no painel.. Feche a fivela

As árvores onde cada nó que não seja folha numa árvore binária tem sub- árvores esquerda e direita não vazias são conhecidas como árvores estritamente binárias.. apresenta

• Em cada sub-árvore com raiz em x, os itens na sub-árvore à esquerda de x são menores x e os itens na sub-árvore à direita de x são maiores ou iguais a x. • Este tipo

Obtida a escritura pública deverá ser enviada ao Presidente do Tribunal para conhecimento e encaminhamento à Coordenadoria Administrativa para o devido registro

Um técnico em informática deverá executar as seguintes tarefas de apoio à comissão avaliadora, antes do início da prova: checagem prévia do funcionamento das