Post on 22-Apr-2015
Árvores Binárias
Profa. Patrícia A. Jaques
Luiz Gonzaga JrContribuições
Algumas transparências são da profa. Renata Galante da II/UFRGScom permissão
Nivelamento - PIPCA 2012/1 2
Árvore Binária
• É uma árvore onde cada nó pode conter nenhum, 1 ou 2 filhos apenas– Grau=2.
• Uma árvore binária é formada por nós onde cada nó contém um valor, um ponteiro esquerdo e um ponteiro direito.
A
D
CB
E F
G
Nivelamento - PIPCA 2012/1 3
Árvore Binária
• O nó A é a raiz da árvore. Como B é a raiz da sua sub-árvore esquerda dizemos que B é o filho esquerdo de A, do mesmo modo, C é o filho direito de A. Por isso A é o pai dos nós B e C e estes dois são irmãos.
A
D
CB
E F
G
filho esquerdo de A filho direito de A
Nivelamento - PIPCA 2012/1 4
EstritamenteBinária
Tipos de Árvores Binárias: Estritamente Binária
• Ocorre quando todo nó que não é folha tiver sub-arvores esquerda e direita não vazias.– todo nó tem 0 ou 2 filhos.
• Número de nós em uma árvore estritamente binária com n folhas:– 2n-1
bibliografia:Szwarcfiter, J. & Markenzon, Lilian. Estrturas de Dados e seus Algoritmos. Rio de Janeiro: LTC, 1994
Nivelamento - PIPCA 2012/1 5
Binária Completa
Tipos de Árvores Binárias: Binária Completa
• É uma árvore estritamente binária onde todos os nós folhas se encontram ou no último ou no penúltimo nível da árvore.
bibliografia:Szwarcfiter, J. & Markenzon, Lilian. Estrturas de Dados e seus Algoritmos. Rio de Janeiro: LTC, 1994
Nivelamento - PIPCA 2012/1 6
Binária Cheia
Tipos de Árvores Binárias: Binária Cheia
• Árvore estritamente binária onde os nós folhas se encontram no último nível.
• O número total de nós em (tn) em uma árvore binária cheia de altura h é:– tn=2h-1
• Assim, pela mesma fórmula podemos afirmar que, numa árvore binária completa, a altura h de uma árvore é dada por:– h= log2(tn+1)
• Embora a árvore binária completa possua muitos nós, a distância da raiz até qualquer folha (profundidade) é relativamente pequena.
bibliografia:Szwarcfiter, J. & Markenzon, Lilian. Estrturas de Dados e seus Algoritmos. Rio de Janeiro: LTC, 1994
Nivelamento - PIPCA 2012/1 7
A
CB
D GFE
Endereço da raiz
A
CB
GFED
Árvores Binárias: representação
chave do nó
referência para filho esquerdo referência
para filho direito
Nivelamento - PIPCA 2012/1 8
Aplicação de Árvores Binárias
• Representação de expressões aritméticas em árvores binárias– Dada uma expressão aritmética, sua representação
em árvore binária é feita de tal forma que a ordem de prioridade das operações fica implícita.
Regra:• O operador de menor prioridade aparece na
raiz;• A sub-expressão à esquerda desse operador
dá origem à sub-árvore esquerda;• A sub-expressão à direita do operador dá
origem à sub-árvore direita.
Nivelamento - PIPCA 2012/1 9
Representação de expressões aritméticas em árvores binárias
+
A *
CB
Ex: A + B * C
Obs. Operandos sempre aparecem como folhas; operadores, nunca.
Nivelamento - PIPCA 2012/1 10
Representação de expressões aritméticas em árvores binárias
• Caminhamentos: • Em pos-ordem Notação polonesa pós fixada.
– Ex: A B C * + (na primeira expressão);
• Em in-ordem Forma original (infixada) sem parênteses.– Ex: A + B * C (primeira expressão);
• Em pré-ordem Notação polonesa prefixada (sem grande utilidade).– Ex: + A * B C (primeira expressão);
• Obs. A notação polonesa pós fixada é muito útil (principalmente para as máquinas), pois exibe os operadores na ordem de execução das operações.
Nivelamento - PIPCA 2012/1 11
Algoritmo para gerar uma árvore binária a partir de uma expressão aritmética em notação infixa
Procedimento Interpretar(string)->ponteiroCOMEÇO 1. fazer uma árvore com a string como único nó; 2. procurar por operadores de MENOR precedência (+ ou -) que
não estejam dentro de “(“ e “)”, pois estes serão considerados um operando.se tem, substitua a árvore por uma com o operador no nó raiz e com
os operandos nos nós folha. (se você procurar o PRIMEIRO operando com menor precedência, basta dividir a árvore como explicado e aplicar esse procedimento de procurar operador de menor precedência recursivamente no nó da DIREITA). Lembre-se que um operando é um número, uma variável uma expressão entre parêntesis ou uma função.
3. repetir o procedimento 2 com operadores de MÉDIA precedência (* e /) SÓ EM NÓS FOLHA;
4. repetir o procedimento para operadores com MAIOR precedência (só o ^);
5. percorrer os nos folha (todos contém operandos) se o operando é número ou variável, não mexe. se o operando é expressão entre parêntesis, substituir esse nó por
Interpretar(expressão sem os parêntesis); FIM Interpretar.
Nivelamento - PIPCA 2012/1 12
Algoritmo para avaliar a árvore (calcular a expressão)
Algoritmo eval (BinaryTreeNode v) {/* recebe nó raiz como parâmetro: a classe BinaryTreeNode, alémdos ponteiros para filhos esquerdo e direito, também contémoutros dois atributos:
1) char operador;2) double operando;real retorno 0;if v == null { não tem árvore - devolve valor zero }then retorno 0;else if v.operador==’’ /* se operando é igual ‘’, então nóguarda operando*/then return v.operando; //devolve valor contido no nodoelse switch a.operador { //operador - executa operação
case “+”: retorno eval(v.left) + eval(v.right);case “-” : retorno eval(v.left) - eval(v.right);case “*” : retorno eval(v.left) * eval(v.right);case “/” : retorno eval(v.left) / eval(v.right);
}return retorno;
}
Nivelamento - PIPCA 2012/1 13
Árvore Binária de Pesquisa
• Também chamada de:– árvore binária de busca – ou árvore binária ordenada
• apresentam uma relação de ordem entre os nodos
• ordem é definida por um campo denominado chave
• não permite chaves duplicadas, ou seja, cada nó tem um valor de chave diferente
esq chave dir
Nivelamento - PIPCA 2012/1 14
Árvore Binária de Pesquisa
raiz500
300 800
150 400 900600
filho da esquerda:valor da chave menor que o valor da chave do nó pai
filho da direita:valor da chave maior que o valor da chave do nó pai
não há chaves duplicadas
fazer exercício: criar árvore inserindo nós na seguinte ordem:f, a, t, b, e, u, i, c, d, a, e
Nivelamento - PIPCA 2012/1 15
Classe representando um nó
public class BSTNode { protected int key; protected BSTNode left, right;
public BSTNode() { left = right = null; } public BSTNode(int num) { this(num,null,null); } public BSTNode(int num, BSTNode lt,
BSTNode rt) { this.key = num; left = lt; right = rt; } public int getKey() {
return key; } public void setKey(int key) {
this.key = key; }
public BSTNode getLeft() {return left;
}public void setLeft(BSTNode left) {
this.left = left;}public BSTNode getRight() {
return right;}public void setRight(BSTNode right) {
this.right = right;} }
Nivelamento - PIPCA 2012/1 16
Classe representando árvore
public class BST { private BSTNode root = null;
public BST() { } public void clear() { root = null; } public boolean isEmpty() { return root == null; } public BSTNode getRootNode (){ return root; }
Nivelamento - PIPCA 2012/1 17
Busca de um valor
• A procura de um valor em uma árvore binária é algo mais rápido do que a procura em listas encadeadas ou vetores.
• Para cada nó, compare a chave a ser localizada com o valor armazenado no nó correntemente apontado. – Se a chave for menor, vá para a sub-árvore esquerda e tente
novamente, – senão vá para a sub-árvore direita.
• A busca pára quando for encontrado o nó ou quando não há mais meios de continuar (nó folha), pois a chave não está na árvore.
• A complexidade pode ser medida pelo número de comparações feitas durante o processo de busca. Isso depende do número de nós encontrados no único caminho que leva da raiz ao nó procurado. Então a complexidade, depende da forma da árvore e da posição do nó procurado na árvore. O número médio de comparações em uma busca é (lg n), pois a altura de uma árvore binária completa (perfeitamente balanceada) é h=lg (n+1) .
Nivelamento - PIPCA 2012/1 18
Busca
public BSTNode search (int el) { return search(root,el);}
private BSTNode search (BSTNode p, int el) { while (p != null) { /* se valor procurado == chave do nó, retorna referência ao nó
*/ if (el==p.key) return p; /* se valor procurado < chave do nó, procurar na sub-árvore esquerda deste nó */ else if (el<p.key) p = p.left; /* se valor procurado > chave do nó, procurar na sub-árvore direita deste nó */ else p = p.right; } // caso chave não foi achada, retorna null return null;}
Nivelamento - PIPCA 2012/1 19
Inserindo uma nova chave
public boolean insert (int el) { BSTNode p = root, prev = null; // caso o valor já exista na árvore, não inserir e
retornar false if (search(el)!=null) return false; // procurando um lugar para colocar o novo nó while (p != null) { prev = p; if (el<p.key) p = p.left; else p = p.right; } // se árvore vazia if (root == null) root = new BSTNode(el); else if (prev.key<el) prev.right = new BSTNode(el); else prev.left = new BSTNode(el); return true; }
Nivelamento - PIPCA 2012/1 20
Caminhamento (percurso) em árvore binária de pesquisa
• É o processo de visitar cada nó da árvore exatamente uma vez.
• Visitar: Fazer algo com o nó como exibi-lo, gravá-lo, etc.
• O percurso pode ser interpretado como colocar todos os nós em uma linha ou a linearização da árvore.
• Os percursos podem ser em extensão ou em profundidade. – Percursos em extensão: visitam todos os nós de
cada nível, nível por nível (indo do mais alto ao mais baixo, ou vice-versa).
– Percursos em profundidade: percorre os caminhos das árvores. Percorre primeiramente todo o caminho mais a esquerda, e assim por diante.
Nivelamento - PIPCA 2012/1 21
Caminhamento (percurso) em árvore binária de pesquisa
• Os três mais comuns tipos de percursos em profundidade, definidos recursivamente, são:– PRÉ-ORDEM (raiz-esquerda-direita):
• visita o nó; • percorre a sub-árvore esquerda; • percorre a sub-árvore direita;
– PÓS-ORDEM (esquerda-direita- raiz): • percorre a sub-árvore esquerda; • percorre a sub-árvore direita; • visita o nó;
– IN-ORDEM (esquerda-raiz-direita): • percorre a sub-árvore esquerda; • visita o nó; • percorre a sub-árvore direita.
Nivelamento - PIPCA 2012/1 22
Percurso in-ordem
public void inorder() { inorder(root);}
private void inorder (BSTNode p) { if (p != null) { inorder(p.left); System.out.print(p.key + " "); inorder(p.right); }}
Nivelamento - PIPCA 2012/1 23
Percurso em pre-ordem
public void preorder() { preorder(root);}
private void preorder(BSTNode p) { if (p != null) { System.out.print(p.key + " "); preorder(p.left); preorder(p.right); }}
Nivelamento - PIPCA 2012/1 24
Percurso em pós-ordem
public void postorder() { postorder(root);}
private void postorder(BSTNode p) { if (p != null) { postorder(p.left); postorder(p.right); System.out.print(p.key + " "); } }
Nivelamento - PIPCA 2012/1 25
Remoção de um nó
• Na remoção 3 situações podem ocorrer :Caso 1: Exclusão de uma folhaO nó é uma folha e não tem filhos: O ponteiro do seu
pai é ajustado para nulo.
50
30 80
15 40 9060
50
30 80
40 9060
antes depois
Nivelamento - PIPCA 2012/1 26
Remoção de um nó
Caso 2: O nó tem um filho: o ponteiro do pai aponta para o filho deste
50
30 80
40 9060
50
40 80
9060
antes depois
Nivelamento - PIPCA 2012/1 27
Remoção de um nó
• Caso 3: O nó tem 2 filhos. Neste caso podemos fazer a remoção de duas maneiras:– remoção por cópia;– remoção por fusão
50
30 80
15 40 9060
antes
Nivelamento - PIPCA 2012/1 28
Remoção por cópia
• remove uma chave k1 (chave do nó que se quer remover):– sobrescrevendo-a por uma outra chave k2 (o
maior valor na sub-árvore esquerda, pois este vai ser maior que todos os valores da sub-árvore esquerda)
– e então removendo o nó que contem k2 (que será um dos casos simples: folha, ou nó com apenas um filho).
Nivelamento - PIPCA 2012/1 29
Remoção por cópia
1 2 3
Final
Nó que retorna referência ao nó pai de uma chave
protected BSTNode searchFather (int el) { BSTNode p = root; BSTNode prev = null;
// acha o nó p com a chave el while (p != null && !(p.key==el)) { prev = p; if (p.key<el) p = p.right; else p = p.left; } if (p!=null && p.key==el) return prev; return null; }
Nivelamento - PIPCA 2012/1 31
Remoção por cópia public void deleteByCopying (int el) { BSTNode node, father = null; node = search (el) ; // procura nó a ser deletado if (node != null && node.key==el) { if (node!=root) father = searchFather (el); // procura pai do nó a ser deletado if (node.right == null){ // nó não tem filho direito (caso 2 ou caso 1); if (node==root) root= node.left; else if (father.left == node) father.left = node.left; else father.right = node.left; } else if (node.left == null) { // nó não tem filho esquerdo (caso 2) if (node==root) root= node.right; else if (father.left == node) father.left = node.right; else father.right = node.right; } else { // nó tem ambos os filhos: fazer remoção por cópia BSTNode tmp = node.left; // 1. pegando sub-arvore esquerda while (tmp.right != null) // 2. acha a posição mais a direita da sub-árvore esquerda
do nó tmp = tmp.right;
deleteByCopying (tmp.key); // remove por copia o nó que possui o maior valor // da sub-arvore esquerda do nó a ser deletado node.key = tmp.key; // copia o valor da chave do maior nó da subárvore esquerda } } else if (root != null) System.out.println("el " + el + " is not in the tree"); else System.out.println("the tree is empty"); }
Nivelamento - PIPCA 2012/1 32
Remoção por fusão
• A solução consiste em fusionar as duas sub-árvores do nó a ser deletado em uma.
• Para tanto, como na organização da árvore binária, todos os valores da sub-árvore a esquerda são menores que os valores da sub-árvore a direita, deve se encontrar o maior valor na sub-árvore esquerda e torná-lo a raiz da sub-árvore direita. Também pode-se procurar o nó com menor valor da sub-árvore direita.
• Remove a chave, removendo o nó que contém a chave. E o pai do nó removido passa a apontar para a nova sub-árvore.
Nivelamento - PIPCA 2012/1 33
Remoção por fusão
1 2 3
Final
Nivelamento - PIPCA 2012/1 34
Remoção por fusãopublic void deleteByMerging (int el) { BSTNode tmp, node,father = null; node = search (el) ; // procura nó a ser deletado if (node != null && node.key==el) {
// procura pai do nó a ser removido if (node!=root) father = searchFather (el); if (node.right == null){ // nó não tem filho direito (caso 2); if (root==node) root=node.left; else if (father.left == node) father.left = node.left; else father.right = node.left; } else if (node.left == null) { // nó não tem filho esquerdo (caso 2) if (root==node) root=node.right; else if (father.left == node) father.left = node.right; else father.right = node.right; } else { // se tem dois filhos, faz deleção por fusão tmp = node.left; // pega sub-arvore esquerda while (tmp.right != null) tmp = tmp.right; // pega filho mais a direita da sub-arvore esquerda
tmp.right = node.right; // o filho mais a direita da sub-arvore esquerda passa a
ter // como filho direito o filho direito do nó a ser deletado
if (root==node) root = node.left; else if (father.left == node) father.left = node.left; else father.right = node.left; } } else if (root != null) System.out.println("el " + el + " is not in the tree"); else System.out.println("the tree is empty"); }
Heap
• É uma estrutura de dados baseada em árvore (especializada)
• Propriedade do heap se B é filho de um nó A, então a chave(A) >= chave (B) (maior elemento na raiz). Chamada max heap. Pode-se comparar ao contrário, dai temos o min heap.
• Usado para implementar priority queues• Applet: vamos ver o funcionamento
http://people.ksp.sk/~kuko/bak/index.html
Max HEAP
Demo: construção da heap
• http://students.ceid.upatras.gr/~perisian/data_structure/HeapSort/heap_applet.html
Implementação
• A remoção do heap é feita ordenando os elementos.
• Implementação do heap através de array a:
• Indexação– filhos a[2i+1] e a[2i+2]– pais a[floor((i−1)/2)]
Heap binário -> usa-se árvore binária
Heap binário
• É um heap criado usando-se árvore binária, com duas restrições:– Propriedade de shape: árvore completa (todos os
níveis da árvores preenchidos, exceto possivelmente o último...) - preenchimento da esquerda para a direita
– Propriedade de heap: cada né á maior do que seus filhos
• Operações:– Inserção– Remoção
Nivelamento - PIPCA 2012/1 40
Exemplos
• Inserção
• Remoção da raiz do heap (maior elemento)
Nivelamento - PIPCA 2012/1 41
Outros tipos
Kd-tree 3-d2-d
Quadtree Octree
Nivelamento - PIPCA 42
BSP