ENGENHARIA DE SOFTWARE

Propaganda
Arvores
Uma das mais importantes classes de estruturas de dados em computação são as
árvores. Aproveitando-se de sua organização hierárquica, muitas aplicações são
realizadas usando-se algoritmos relativamente simples, recursivos e de eficiência
bastante razoável.
Uma árvore é uma estrutura de dados que se caracteriza por uma relação de
hierarquia entre os elementos que a compõem.Como exemplos de estruturas em árvores:
• O organograma de uma empresa;
• A divisão de um livro em capítulos, seções, tópicos, etc;
• A árvore genealógica de uma pessoa.
Uma árvore consiste de nós conectados por bordas (ou galhos). Os nós são geralmente
representados por círculos e os galhos por linhas conectando os círculos.
As árvores têm sido estudadas extensivamente e são utilizadas em várias áreas de
conhecimento. Elas são na verdade uma instância de uma categoria mais geral, os
grafos.
Um galho por sua vez, é geralmente implementado como um ponteiro dentro de um nó
que aponta para outro nó. Normalmente há um nó na fila de cima de uma árvore, com os
galhos conectando mais nós na segunda fila, ainda mais na terceira, etc. Desta forma, as
árvores são pequenas em cima e grandes embaixo. Ela pode parecer de cabeça para
baixo se comparada a uma árvore de verdade, mas é a forma mais natural de um
programa trabalhar, indo da ponta menor para baixo.
Existem diferentes tipos de árvores. Algumas possuem mais de dois filhos por nó
(veremos adiante o que são filhos). Estudaremos aqui um tipo especializado de árvores,
chamado de árvore binária, onde cada nó possui, no máximo, dois filhos.
A
B
D
C
E
H
G
F
I
J
Árvo
re
Terminologia
Percurso: É a seqüência obtida quando se anda de nó em nó pelos galhos que os
conectam.
Percorrer a árvore: Percorrer uma árvore significa visitar todos os nós em alguma
ordem especificada, por exemplo, na ordem ascendente dos valores-chave.
Raiz: É o nó em cima da árvore. Há somente uma raiz em uma árvore. Para que um
conjunto de nós e galhos possa ser definido como uma árvore, deve haver apenas um
percurso da raiz para qualquer outro nó.
Pai: Qualquer nó (exceto a raiz), possui exatamente um galho indo para cima até outro
nó. Este nó acima é chamado de nó pai.
Filho: Qualquer nó pode ter um ou mais galhos indo para baixo até outros nós. Estes
nós abaixo de um dado nó são chamados de seus filhos.
Folha: Um nó que não possui filhos é chamado de folha. Só pode haver uma raiz em
uma árvore, mas pode haver muitas folhas.
Sub-árvore: Qualquer nó pode ser considerado a raiz de uma sub-árvore, que consiste
de seus filhos, e os filhos destes, etc. Pensando em termos de família, a sub-árvore de
um nó contém todos os seus descendentes.
Visitar um nó: Um nó é visitado quando o controle de um programa chega no nó,
geralmente para realizar alguma operação neste, tal como verificar o valor de seus
dados, modificá-los ou exibi-los. Apenas passar sobre um nó no percurso de um a outro
é considerado visitar o nó.
Nível: O nível de um nó em particular se refere a quantas gerações o nó está da raiz. Se
assumirmos que a raiz está no nível 0, os seus filhos estarão no nível 1, seus netos no
nível 2, etc.
Chaves: É o valor utilizado para procurar pelo item ou realizar operações sobre ele.
Geralmente aparece nos diagramas de árvores, dentro do círculo que representa um nó.
Árvores binárias: Se cada nó em uma árvore pode ter no máximo dois filhos, a árvore
é chamada de árvore binária. Elas são as mais simples, mais comuns, e em muitas
situações as árvores mais utilizadas.
Filho esquerdo e direito: Os filhos de cada nó em uma árvore binária são chamados de
filho esquerdo e filho direito. Um nó em uma árvore binária não necessariamente possui
dois filhos; pode ter apenas um filho esquerdo, ou apenas um filho direito, ou pode não
ter filhos (o que significa que é uma folha).
A maioria das operações em árvores envolve descer até o nível mais baixo da árvore.
Em uma árvore completa, metade dos nós está no nível inferior. Desta forma, cerca de
metade de todas as procuras, inserções ou exclusões requerem encontrar um nó no nível
mais baixo. Durante uma procura, visitamos um nó de cada nível.
Percorrer uma árvore não é tão rápido, mas esta não é uma operação muito freqüente de
se realizar num banco de dados grande comum. Um percurso é necessário na análise de
expressões algébricas, mas neste caso a árvore provavelmente não será muito grande.
Podemos concluir que as árvores oferecem a mais alta eficiência para todas as
operações de armazenamento de dados comuns.
Árvores desequilibradas
Uma árvore desequilibrada possui a maioria dos seus nós em um lado da raiz. As
árvores tornam-se desequilibradas por causa da ordem na qual os itens de dados são
inseridos. Se os itens são inseridos aleatoriamente, a árvore estará mais ou menos
equilibrada. Entretanto, se uma seqüência ascendente ou descendente de dados for
gerada, todos os valores serão filhos direitos ou filhos esquerdos, respectivamente.
Se uma árvore é criada por itens de dados cujos valores-chave chegam em ordem
aleatória, o problema das árvores desequilibradas pode não ser grande para árvores
maiores, por que as chances de ocorrer uma longa execução dos números na seqüência
são pequenas. Por outro lado, se os dados chegam em ordem, a eficiência da árvore
poderá ser seriamente prejudicada. Existem métodos para resolver o problema das
árvores desequilibradas, como as árvores red-black.
90
42
75
23
83
31
10
7
95
18
78
87
Árvore
Desequilibrad
a
Árvore Binária
Chamamos de Árvores Binárias (AB), um conjunto finito T de nós ou vértices, onde
existe um nó especial chamado raiz e os restantes podem ser divididos em 2
subconjuntos disjuntos, chamados de sub-árvores esquerda e direita que também são
Árvores Binárias. Em particular T pode ser vazio.
Árvore binária é um tipo de estrutura de dados que, uma vez ordenada, permite
pesquisa, inserção e exclusão de forma extremamente rápida.
Exemplo:
Cada nó numa arvore binária, pode ter então 0, 1 ou 2 filhos. Existe portanto uma
hierarquia entre os nós. Com exceção da raiz, todo nó tem um nó pai.
Dizemos que o nível da raiz é 1 e que o nível de um nó é o nível de seu pai mais 1. A
altura de uma arvore binária é o maior dos níveis de seus nós.Dizemos que um nó é
folha da arvore binária se não tem filhos.
O tipo de árvore que estaremos vendo aqui é conhecido como árvore de procura binária.
A característica que define uma árvore de procura binária é a seguinte: o filho esquerdo
de um nó deve ter uma chave menor do que seu pai e o filho direito de um nó deve ter
uma chave maior ou igual ao seu pai.
53
30
14
9
72
39
23
84
61
79
Os exemplos abaixo , mostram que podemos ter várias arvores binárias de busca
(pesquisa) com os mesmos elementos , ou seja o objetivo é sempre termos uma arvores
binárias de busca de menor altura. Nesse sentido a primeira arvores binárias de busca é
melhor que a segunda.
Árvores binárias como listas ligadas
Podemos representar uma arvore binária de busca com uma lista ligada, onde cada
elemento tem os seguintes campos:
info - campo de informação
eprox - apontador para a sub-árvore esquerda
dprox - apontador para a sub-árvore direita
A complexidade é a altura da árvore, portanto é conveniente que a árvore tenha sempre
altura mínima.
Pesquisa em uma Arvore Binária
Ao contrário de uma lista encadeada, uma árvore binária pode ser percorrida de muitas
maneiras diferentes. Uma maneira particularmente importante é a ordem
Esquerda (e) –raiz (r) –direita (d) .
Na varredura e-r-d , visitamos :
1. a subárvore esquerda da raiz, em ordem e-r-d;
2. depois a raiz;
3. depois a subárvore direita da raiz, em ordem e-r-d.
Inserção numa Arvore Binária
Um novo elemento é inserido sempre como uma folha de uma arvore binária de busca.
É necessário descer na arvore binária até encontrar o nó que será o pai deste novo nó.
Remoção numa Arvore Binária
A remoção é um pouco mais complexa que a busca ou inserção. O problema da
remoção física de um nó é que é necessário encontrar um outro nó para substituir o
removido, caso o nó a ser removido tenha filhos.
1) O nó a ser removido não tem filhos (folha)
2) O nó a ser removido tem filhos direito e esquerdo
Os candidatos à substituto são obtidos percorrendo-se a arvore binária , um à esquerda e
tudo a direita até achar nó com dprox NULL ou um a direita e tudo à esquerda até achar
nó com eprox NULL.
Além de alterar o ponteiro para o nó que vai ser substituído, é necessário mover o
conteúdo deste nó para o nó a remover e fisicamente remover o substituto. O pai do
substituto assume os seus filhos.
Arvore Balanceada (AVL)
Uma árvore binária é balanceada (ou equilibrada) se, em cada um de seus nós, as subárvores esquerda e direita tiverem aproximadamente a mesma altura.
Convém trabalhar com árvores balanceadas sempre que possível. Mas isso não é fácil se
a árvore aumenta e diminui ao longo da execução do seu programa.
Idealmente queremos que a árvore esteja balanceada, ou seja, para um nodo p qualquer,
a altura da subárvore esquerda é aproximadamente igual à altura da subárvore direita.
Obviamente há um custo extra de processamento para manter a árvore balanceada, mas
que é compensado quando os dados armazenados precisam ser recuperados muitas
vezes.
A idéia de manter uma árvore binária balanceada dinamicamente, ou seja, enquanto os
nodos estão sendo inseridos foi proposta em 1962 por 2 soviéticos chamados AdelsonVelskii e Landis. Este tipo de árvore ficou então conhecida como árvore AVL, pelas
iniciais dos nomes dos seus inventores.
Por definição uma árvore AVL é uma árvore binária de pesquisa onde a diferença em
altura entre as subárvores esquerda e direita é no máximo 1 (positivo ou negativo).
Assim, para cada nodo podemos definir um fator de balanceamento (FB), que vem a ser
um número inteiro igual a
FB(nodo p) = altura(subárvore direita p) - altura(subárvore esquerda p)
Balanceamento em Arvores AVL
Como fazemos então para manter uma árvore AVL balanceada? Inicialmente inserimos
um novo nodo na árvore normalmente. A inserção deste novo nodo pode ou não violar a
propriedade de balanceamento. Caso a inserção do novo nodo não viole a propriedade
de balanceamento podemos então continuar inserindo novos nodos. Caso contrário
precisamos nos preocupar em restaurar o balanço da árvore. A restauração deste balanço
é efetuada através do que denominamos ROTAÇÕES na árvore

Rotação Simples para Direita

Rotação Simples para Esquerda

Rotação Dupla para Direita: é composta de uma rotação simples à direita,
seguida de uma rotação simples à esquerda.

Rotação Dupla para Esquerda: é composta de uma rotação simples à esquerda,
seguida de uma rotação simples à direita.
Uma aplicação com Arvore Binária
As árvore binárias são estruturas importantes toda vez que uma decisão binária deve ser
tomada em algum ponto de um algoritmo. Vamos agora, antes de passar a algoritmos
mais complexos, mostrar uma aplicação simples de árvores binárias. Suponhamos que
precisamos descobrir números duplicados em uma lista não ordenada de números. Uma
maneira é comparar cada novo número com todos os números já lidos. Isto aumenta em
muito a complexidade do algoritmo. Outra possibilidade é manter uma lista ordenada
dos números e a cada número lido fazer uma busca na lista. Outra solução é usar uma
árvore binária para manter os números. O primeiro número lido é colocado na raiz da
árvore. Cada novo número lido é comparado com o elemento raiz, caso seja igual é uma
duplicata e voltamos a ler outro número. Se é menor repetimos o processo com a árvore
da direita e se maior com a árvore da esquerda. Este processo continua até que uma
duplicata é encontrada ou uma árvore vazia é achada. Neste caso, o número é inserido
na posição devida na árvore. Considere que os números
7 8 2 5 8 3 5 10 4
Eficiência com Arvores Binárias
A maioria das operações em árvores envolve descer até o nível mais baixo da árvore.
Em uma árvore completa, metade dos nós está no nível inferior. Desta forma, cerca de
metade de todas as procuras, inserções ou exclusões requerem encontrar um nó no nível
mais baixo.
Durante uma procura, visitamos um nó de cada nível. Percorrer uma árvore não é tão
rápido, mas esta não é uma operação muito freqüente de se realizar num banco de dados
grande comum. Um percurso é necessário na análise de expressões algébricas, mas
neste caso a árvore provavelmente não será muito grande.
Podemos concluir que as árvores oferecem a mais alta eficiência para todas as
operações de armazenamento de dados comuns.
Download