Árvores de busca / 1 Árvores binárias de busca · nas operações de inserção e remoção de...

14
/ Árvores de busca / 1 Árvores binárias 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, apanhamos os elementos por ordem crescente: 3, 12, 14, 32, 42, 43, 44, 47, 49, 51, 58, 63. Uma árvore binária deste tipo é de busca (em relação ao campo chave) se cada X tem a seguinte propriedade: a chave de X é 1. maior ou igual à chave de qualquer nó na subárvore esquerda de X e 2. menor ou igual à chave de qualquer nó na subárvore direita de X. Em outras palavras, se x é (o endereço de) um nó qualquer então y->chave x->chave z->chave. para todo nó y na subárvore esquerda de x e todo nó z na subárvore direita de x. Há uma outra maneira, talvez mais clara, de descrever esta propriedade: a ordem e-r-d das chaves é crescente. 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. Operações Operações em uma árvore binária requerem comparações entre nós. Busca A busca começa examinando o nó raiz. Se a árvore está vazia, o valor procurado não pode existir na árvore. Caso contrário, se o valor é igual a raiz, a busca foi bem sucedida.

Upload: lamdieu

Post on 05-Dec-2018

213 views

Category:

Documents


0 download

TRANSCRIPT

/ Árvores de busca / 1

Árvores binárias 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, apanhamos os elementos por ordem crescente: 3, 12, 14, 32, 42, 43, 44, 47, 49, 51, 58, 63.

Uma árvore binária deste tipo é de busca (em relação ao campo chave) se cada nó X tem a seguinte propriedade: a chave de X é

1. maior ou igual à chave de qualquer nó na subárvore esquerda de X e 2. menor ou igual à chave de qualquer nó na subárvore direita de X.

Em outras palavras, se x é (o endereço de) um nó qualquer então

y->chave ≤ x->chave ≤ z->chave.

para todo nó y na subárvore esquerda de x e todo nó z na subárvore direita de x.

Há uma outra maneira, talvez mais clara, de descrever esta propriedade: a ordem e-r-d das chaves é crescente.

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.

Operações

Operações em uma árvore binária requerem comparações entre nós. Busca

A busca começa examinando o nó raiz. Se a árvore está vazia, o valor procurado não pode existir na árvore. Caso contrário, se o valor é igual a raiz, a busca foi bem sucedida.

/ Árvores de busca / 2 Se o valor é menor do que a raiz, a busca segue pela subárvore esquerda. Similarmente, se o valor é maior do que a raiz, a busca segue pela subárvore direita. Esse processo é repetido até o valor ser encontrado ou a subárvore ser nula (vazia). Se o valor não for encontrado até a busca chegar na subárvore nula, então o valor não deve estar presente na árvore. 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

A localização do ‘ponto de inserção’ é semelhante à busca por um valor na árvore. • Após a inserção do novo elemento, a árvore deve manter as propriedades de ‘árvore binária de busca’. • O nó inserido é sempre uma folha.

/ Árvores de busca / 3

Remover um nó

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.

/ Árvores de busca / 4

Caso 1 – O nó a ser removido é uma folha

• Caso 2 – O nó X a ser removido não é uma folha – 2a. esqDir = nó mais à direita da sub-arvore esquerda; conteúdo do no X = conteúdo de esqDir; remover (recursivamente) o nó esqDir;

– 2a. esqDir = nó mais à direita da sub-arvore esquerda; conteúdo do no X = conteúdo de esqDir; remover (recursivamente) o nó esqDir;

/ Árvores de busca / 5

– 2b. dirEsq = nó mais à esquerda da sub-arvore direita; conteúdo do no X = conteúdo de dirEsq; remover (recursivamente) o nó dirEsq;

Exemplo

arvore.h

#ifndef ARVORE_H_INCLUDED

#define ARVORE_H_INCLUDED

class BinarySearchTree {

private:

// declaracao de tipos

struct TreeNode {

// tipo de dado colocado na arvore

int valor;

/ Árvores de busca / 6 // 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();

};

#endif // ARVORE_H_INCLUDED

arvore.cpp

#include <iostream>

#include <cstdlib>

#include <iomanip>

#include "arvore.h"

/ Árvores de busca / 7 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() {

return false;

}

// 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() {

/ Árvores de busca / 8 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;

while (t != NULL && t->valor != x)

if(x < t->valor)

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;

/ Árvores de busca / 9 r->noEsquerdo = NULL;

r->noDireito = NULL;

if(p == NULL)

root = r; // arvore vazia

else if(x < p->valor)

p->noEsquerdo = r;

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

/ Árvores de busca / 10 void BinarySearchTree::excluir(int x, TreePointer &p) {

TreePointer q;

if(p == NULL) {

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)

{

if(r->noEsquerdo != NULL)

excluirMenor(q,r->noEsquerdo);

else {

/ Árvores de busca / 11 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);

tree.imprimir();

if (tree.procurar(5))

cout << "Encontrou" << endl;

else

cout << "Nao encontrou" << endl;

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

/ Árvores de busca / 12 cout << "Maior elemento: " << tree.maximo() << endl;

return 0;

}

Árvores de busca balanceadas • Árvore binária balanceada: uma árvore binária é dita balanceada se: – o número de nós das sub-árvores esquerda difere no máximo de 1 do número de nós da sub-árvore direita. – os sub-árvores esquerda e direita são balanceadas. • Propriedade: a altura de uma árvore binária balanceada com n nós é igual a log2n+1

Árvores AVL • Árvore binária de busca auto balanceável • Autores: Adelson-Vesky e Landis (1962) • Idéia básica: cada nó mantém uma informação adicional, chamada fator de balanceamento, que indica a diferença de altura entre as sub-árvores esquerda e direita. • As operações de inserção e remoção mantém o fator de balanceamento entre -1 e +1. • Embora não sejam perfeitamente balanceadas, é possível demonstrar que a altura de uma árvore AVL ainda é proporcional a log2n (~ 1.5* log2n). Nas operações de inserção e remoção de elementos, o balanceamento da árvore resultante é ajustado através da operação de rotação, que preserva a ordenação da árvore.

As operações de inserção e remoção de nós numa árvore AVL, incluindo as eventuais operações de rotação, são realizadas em tempo proporcional a log2n.

Rotação à esquerda void rotacaoEsquerda(tnodeptr * t){

tnodeptr x = (*t); /* != NULL */

tnodeptr y = x->dir; /* != NULL */

x->dir = y->esq;

if(y->esq != NULL) (y->esq)->pai = x;

y->pai = x->pai;

/ Árvores de busca / 13 if(x->pai == NULL) *t = y;

else if(x == (x->pai)->esq) (x->pai)->esq = y;

else (x->pai)->dir = y;

y->esq = x;

x->pai = y;

}

Rotação à direita void rotacaoDireita(tnodeptr * t){

tnodeptr x = (*t); /* != NULL */

tnodeptr y = x->esq; /* != NULL */

x->esq = y->dir;

if(y->dir != NULL) (y->dir)->pai = x;

y->pai = x->pai;

if(x->pai == NULL) *t = y;

else if(x == (x->pai)->dir) (x->pai)->dir = y;

else (x->pai)->esq = y;

y->dir = x;

x->pai = y;

}

/ Árvores de busca / 14

Referências

Internet • http://en.wikipedia.org/wiki/AVL_tree • http://www.site.uottawa.ca/~stan/csi2514/applets/avl/BT.html • http://www.eli.sdsu.edu/courses/fall96/cs660/notes/avl/avl.html • http://www.brpreiss.com/books/opus4/html/page320.html

Bibliografia CELES FILHO, Waldemar; Cerqueira, Renato; Rangel, José Lucas. Introdução a estrutura de dados: com técnicas de programação em C. Rio de Janeiro : Elsevier, 2004 − 7a. Impressão.

DROZDEK, Adam; tradução Luiz Sérgio de Castro Paiva. Estrutura de dados e algoritmos em C++. São Paulo: Cengage Learning, 2010.

MANZANO, José Augusto N. G.; Oliveira, Jayr Figueiredo de. Algoritmos: lógica para desenvolvimento de programação de computadores. São Paulo : Érica, 2010.

SAVITCH, Walter. C++ absoluto. São Paulo: Addison Wesley, 2004.

SILVA FILHO, Antônio Mendes da. Introdução à programação orientada a objetos com C++. Rio de Janeiro : Elsevier, 2010.

SZWARCFITER, L. J.e MARKENZON, L. Estrutura de Dados e Seus Algoritmos. Rio de Janeiro: LTC - Livros Técnicos e Científicos, 1994. 2. edição revista. Cap. 4 − 5.

TENENBAUM, Aaron M.; LANGSAM, Yedidyah; AUGENSTEIN, Moshe J.; tradução Teresa Cristina Félix de Souza. Estruturas de dados usando C. São Paulo : MAKRON Books, 1995. Cap. 7.

VELOSO, P; SANTOS, C; AZEREDO, P e FURTADO, A. Estrutura de Dados. Rio de Janeiro: Campus, 2000.

WIRTH, N. Algoritmos e Estrutura de Dados. Rio de Janeiro: LTC - Livros Técnicos e Científicos, 1999. 2. edição revista.