Você está na página 1de 25

03/06/2018

Estruturas de Dados
Árvores Binárias

Prof. Luciana R. Cardoso

Árvores
2

 Ao contrário das listas, filas e pilhas vistas


anteriormente, as quais eram estruturas lineares,
existem outras formas usadas para representar
os dados, que são chamadas genericamente de
“não-lineares”.
 Essa representação permite que sejam feitos
outros tipos de relações entre os dados, como por
exemplo, relação de hierarquia.
 Um dos exemplos mais significativos de
estruturas não lineares que representam as
relações de hierarquia é a Árvore.

Prof. Luciana R. Cardoso

1
03/06/2018

Árvores
3

 A principal característica desta estrutura é a


organização das informações, que se faz através de
ramos, ou seja, é uma estrutura de dados que dispõe
seus elementos em forma hierárquica de nós, em
árvore.

 Definição: Uma árvore é um conjunto finito de um


ou mais nós tal que:
 Existe um nó especial denominado raiz
 Os outros nós formam conjuntos disjuntos, onde cada um
destes conjuntos é uma árvore.

 Obs: Todo nó de uma árvore é a raiz de uma sub-


árvore.

Prof. Luciana R. Cardoso

Árvores
4

 Exemplos:
 árvore genealógica, organização hierárquica da
empresa, taxonomia de espécies animais, etc.
 Podemos representar de forma ligada
(ponteiros) ou sequencial (vetores).
 Operações comuns: adicionar, remover, acessar,
percorrer, pesquisar elementos.

Prof. Luciana R. Cardoso

2
03/06/2018

Árvores
5

 Considere agora o seguinte exemplo de árvore


genealógica:

Prof. Luciana R. Cardoso

Árvores - Terminologia
6

 Grau de um nodo: número de subárvores de um nodo. Por


exemplo, na figura anterior, o nodo a tem grau 3, o nodo d
tem grau 2 e o nodo c tem grau 1.
 Grau de uma árvore: o grau de uma árvore (n-áridade) é
definido como sendo o máximo dos graus de todos os seus
nodos. Na ilustração, podemos constatar que o grau da árvore
é 3, pois nenhum nodo tem mais de 3 subárvores.
 Folhas de uma árvore: também chamadas de nodos
terminais, são nodos que possuem grau 0 (zero), ou seja, que
não possuem subárvores. No exemplo dado, são folhas os
nodos e, k, g, l, m, i e j.

Prof. Luciana R. Cardoso

3
03/06/2018

Árvores – Terminologia (continuação)


7

 Filhos de um nodo X: são as raízes das subárvores


do nodo X. Neste caso, o nodo X é o pai. Os filhos do
nodo b são e, f e g; h é o pai de l e m; os filhos do
mesmo pai denominam-se nodos irmãos.
 Ancestrais de um nodo: são todos os nodos ao
longo do caminho, a partir da raiz até o nodo.
 Descendentes de um nodo: são todos os nodos
abaixo deste nodo e que formam caminhos com este
até as folhas.

Prof. Luciana R. Cardoso

Árvores – Terminologia (continuação)


8

 Nível de um nodo: por definição dizemos que a


raiz de uma árvore encontra-se no nível 1 (há quem
considere o primeiro nível como sendo o 0 (zero)).
Estando um nodo no nível n, seus filhos estarão no
nível n+1.
 Altura ou profundidade: é definida como sendo
o máximo dos níveis de todos os seus nodos. Uma
árvore nula tem altura 0 (zero). A árvore da figura
mostrada tem altura igual a 4. A subárvore com raiz
d tem altura 2.

Prof. Luciana R. Cardoso

4
03/06/2018

Árvores – Terminologia (continuação)


9

 Árvore Completa: todas as folhas no mesmo nível.


 Árvore Quase Completa: todas as folhas em nível
n ou n-1.

Prof. Luciana R. Cardoso

Árvores
10

 Uma árvore é homogênea quando todos os seus


nodos possuem as mesmas características de
conteúdo, ou seja, o mesmo grau máximo e o mesmo
tipo de informação; caso contrário ela é então
chamada de heterogênea.

Prof. Luciana R. Cardoso

5
03/06/2018

Exemplo
11

 O nó a é denominado nó raiz e tem


grau 2, pois possui dois filhos, os nós b
e c;
 O nó b tem grau 3 e é considerado nó
pai dos nós d, e e f;
 Os nós d e e são nós terminais, pois
não possuem descendentes;
 A árvore possui grau 3, pois este é o
número máximo de nós descendentes
de um único pai;
 A árvore tem altura igual a 5; já o nó b
tem altura igual a 4 e o nó c tem altura
igual a 2;
 A trajetória para se chegar ao nó k, por
exemplo, é mostrada na árvore através
dos nós marcados em cinza.

Prof. Luciana R. Cardoso

Exercício1 :
12

 Tente construir uma árvore com base nas informações abaixo:


 O nodo C tem grau 3.
 O nodo X é neto de C e filho de B.
 O avô de B é A.
 O nodo A tem altura 0 e T tem altura 1.
 Os antepassados de P são A,T e K, que são também
antepassados de H.
 T tem grau 2, e um dos seus filhos é o nodo S.
 O nodo G tem 2 sub-árvores que são netos de C.
 D é irmão de G, que é uma folha.
 E e F tem graus 0 e 1 respectivamente e são também netos do
nodo C.
 O nodo N tem nível 4.

Prof. Luciana R. Cardoso

6
03/06/2018

Exercício 2
13

 Dada a árvore abaixo, preencha a tabela ao lado:

Prof. Luciana R. Cardoso

Tipos de Árvores
14

 Listas generalizadas: nós com grau maior ou


igual a zero.
 Binárias: nós com grau menor ou igual a dois.

Prof. Luciana R. Cardoso

7
03/06/2018

Árvores Binárias (AB)


15

 Uma árvore binária se caracteriza pelo fato de todos


os seus nodos terem no máximo duas sub-árvores,
ou seja, é uma árvore de grau 2, isto é, nenhum nodo
tem mais que dois filhos.
 Os demais nodos são particionados em estruturas
disjuntas de árvores binárias denominadas T1 e T2:
 T1 é denominada subárvore esquerda e T2 subárvore direita.

Prof. Luciana R. Cardoso

Árvores Binárias
16

 Na figura abaixo, temos duas árvores binárias, sendo


a da esquerda, incompleta e a da direita completa:

Prof. Luciana R. Cardoso

8
03/06/2018

Árvores Binárias
17

 Adicionalmente, para árvores binárias existe um


senso de posição, ou seja, distingue-se entre uma
subárvore esquerda e uma direita. Por exemplo, as
árvores binárias abaixo são consideradas distintas: a
primeira tem a subárvore direita nula e na segunda a
subárvore esquerda que é considerada nula.

Prof. Luciana R. Cardoso

Árvores Binárias
18

 Uma árvore binária, cuja raiz armazena o elemento


R, é denominada árvore de busca binária se:
 todo elemento armazenado na subárvore esquerda é menor
que R;
 nenhum elemento armazenado na subárvore direita é menor
que R;
 as subárvores esquerda e direita também são árvores de
busca binária.

Prof. Luciana R. Cardoso

9
03/06/2018

Árvores Binárias
19

 O próximo exemplo tem o elemento 'd' na raiz, todos


os elementos na subárvore esquerda ('a', 'b' e 'c') são
menores que a raiz, nenhum elemento da subárvore
direita ('d', 'e' e 'f') é menor que a raiz e, no entanto,
ela não é uma árvore de busca binária. Isto é devido
ao fato da subárvore esquerda não ser uma árvore de
busca binária.

Prof. Luciana R. Cardoso

Árvores Binárias
20

 A primeira coisa a se definir para a implementação


de árvores binárias é o formato dos nodos. Como
cada nodo precisa armazenar a informação e
referenciar duas subárvores, podemos defini-lo como
a seguir:
typedef struct NodoArvore* ArvoreBin;
struct NodoArvore{
char info;
ArvoreBin subArvEsq; //subarvore a esquerda
ArvoreBin subArvDir; //subarvore a direita
};

Prof. Luciana R. Cardoso

10
03/06/2018

Árvore Binária
21

 O conceito mais natural de árvore binária na nossa


implementação, portanto, é: um ponteiro para raiz.
Uma árvore binária nula (ou vazia) será
representada por um ponteiro NULL.

Prof. Luciana R. Cardoso

Árvores Binárias
22

 Então, seja uma variável arvore do tipo ArvoreBin e


suponha que os seguintes elementos sejam inseridos
nesta árvore: 'e', 'b', 'f', 'a', 'd' e 'g'. Neste caso,
espera-se a seguinte situação esquemática na
memória:

Prof. Luciana R. Cardoso

11
03/06/2018

Árvores Binárias
23

 As funções para inicializar a árvore binária e testar se


vazia são triviais:
//faz arvore vazia
void fazArvVazia(ArvoreBin& arv){
arv=NULL;
};
//verifica se esta vazia
int arvVazia(ArvoreBin& arv){
return (arv==NULL);
};
Prof. Luciana R. Cardoso

Árvores Binárias
24

 Também o são as funções para se criar ou destruir


um nodo:
NodoArvore* criaNodo(char e){
NodoArvore* aux = new NodoArvore;
if(aux)
aux->info=e;
return aux;
};

void destroiNodo(NodoArvore* p){


if(p) delete p;
};

Prof. Luciana R. Cardoso

12
03/06/2018

Árvores Binárias
25

 Inserir um elemento numa árvore vazia é trivial. Se a


árvore não está vazia e o elemento a inserir é menor
que o elemento da raiz, inserimos na subárvore
esquerda; caso contrário, devemos inserir na
subárvore direita.
 Sendo assim, um novo elemento inserido entrará
sempre na condição de folha.
 Utilize este raciocínio para inserir o elemento 'c' na
árvore mostrada anteriormente.

Prof. Luciana R. Cardoso

Árvores Binárias
26

//insere elemento na arvore


void insere(ArvoreBin& arv, NodoArvore* pn){
if(arvVazia(arv)) {
arv=pn;
pn->subArvEsq = NULL;
pn->subArvDir = NULL;
}
else {
if(pn->info<arv->info)
insere(arv->subArvEsq,pn);
else
insere(arv->subArvDir, pn);
}
};

Prof. Luciana R. Cardoso

13
03/06/2018

Árvores Binárias - Percursos em profundidade


27

 Antes de passarmos a outras operações consideradas


básicas, vamos falar de atravessamento em árvores
binárias.
 Atravessar uma árvore binária significa passar de
forma sistemática por cada um de seus nodos,
realizando um certo processamento. Os tipos mais
conhecidos de atravessamento são três:
 pré-ordem: visite a raiz, então visite a subárvore da esquerda
e depois a subárvore da direita;
 em-ordem ou ordem simétrica: visite a subárvore da
esquerda, então visite a raiz e depois a subárvore da direita; e
 pós-ordem: visite a subárvore da esquerda, então visite a
subárvore da direita e depois a raiz.
Prof. Luciana R. Cardoso

Árvores Binárias - Percursos - exemplo


28

 Exemplo: expressão a * b + d – (f + g) * e

IMPORTANTE: Todos os
algoritmos são recursivos!
Em cada subárvore
visitada, deve-se aplicar a
ordem correta de visita aos
nós!

•pré-ordem (pré-fixado): - d + * a b e * + f g
• em-ordem (infixado ou interfixado): a * b + d – e * f + g
•pré-ordem (pós-fixado): a b * + d f g + * e -

Prof. Luciana R. Cardoso

14
03/06/2018

Árvores Binárias
29

 Percebe-se então que podemos produzir um algoritmo


recursivo para realizar atravessamentos. No caso do
atravessamento em-ordem, seja uma árvore chamada
arv, este atravessamento pode ser definido pelo
algoritmo:
 arv é uma árvore nula: não há nada a fazer;
 Realizamos o atravessamento em-ordem da subárvore
esquerda da raiz de arv;
 Visitamos a raiz;
 Realizamos o atravessamento em-ordem da subárvore
direita da raiz de arv;
 Os algoritmos para atravessamento pré-ordem e pós-
ordem podem ser generalizados segundo o mesmo
raciocínio. Só muda a ordem das ações.
Prof. Luciana R. Cardoso

Exemplo de utilização de Árvores Binárias:


30

 Uma expressão aritmética pode ser armazenada sob a forma de uma


árvore binária, onde a raiz armazenaria a operação a ser efetuada, e
as sub-árvores à esquerda e direita armazenariam os operandos a
serem usados. A expressão ((A+B)/C)*(D-E), por exemplo, ficaria
assim:

Prof. Luciana R. Cardoso

15
03/06/2018

Árvores Binárias
31

 Um uso muito comum de atravessamento é para


imprimir os elementos da árvore. Vejamos 3 funções
em C++ para cada tipo visto:

//impressao em ordem
void impEmOrdem(ArvoreBin arv){
if(arvVazia(arv)) return;
impEmOrdem(arv->subArvEsq);
cout<<arv->info<<' ';
impEmOrdem(arv->subArvDir);
};

Prof. Luciana R. Cardoso

Árvores Binárias
32

//impressao pre ordem


void impPreOrdem(ArvoreBin arv){
if(arvVazia(arv)) return;
cout<<arv->info<<' ';
impPreOrdem(arv->subArvEsq);
impPreOrdem(arv->subArvDir);
}; //impressao pos ordem
void impPosOrdem(ArvoreBin arv){
if(arvVazia(arv)) return;
impPosOrdem(arv->subArvEsq);
impPosOrdem(arv->subArvDir);
cout<<arv->info<<' ';
};

Prof. Luciana R. Cardoso

16
03/06/2018

Árvores Binárias
33

 Um outro caso onde seria necessário atravessar os nodos


de uma árvore é a operação para desalocar a memória
ocupada pelos nodos (destruir a árvore). Utilizaremos o
atravessamento pós-ordem (pode ser utilizado outro tipo
de atravessamento?):
//destroi a arvore utilizando o conceito de pos ordem
void destroi(ArvoreBin arv){
if(arvVazia(arv)) return;
destroi(arv->subArvEsq);
destroi(arv->subArvDir);
delete(arv); //desaloca o nodo raiz
};

Prof. Luciana R. Cardoso

Árvores Binárias
34

 O algoritmo para remoção de um elemento é talvez o mais


trabalhoso que temos. Ainda assim, após analisarmos todos
os possíveis casos, esta operação parecerá bastante simples.
Para facilitar o entendimento, vamos admitir inicialmente
que o elemento a ser removido encontra-se na raiz da árvore
binária arv. Nestas condições, temos três possibilidades:

 a raiz não possui filhos: a solução é trivial. Podemos remover a raiz e


anular arv;
 a raiz possui um único filho: podemos remover o nodo raiz,
substituindo-o pelo seu nodo-filho;
 a raiz possui dois filhos: não é possível que os dois filhos assumam o
lugar do pai. Escolhemos então o nodo que armazena o menor elemento
na subárvore direita de arv.

Prof. Luciana R. Cardoso

17
03/06/2018

Árvores Binárias
35

 Para entender a solução adotada para o terceiro caso,


precisamos lembrar da definição de árvore (de
busca) binária. Segundo esta definição, não podemos
ter elementos maiores que ou iguais à raiz na
subárvore esquerda.
 Também não podemos ter elementos menores que a
raiz na subárvore direita.
 Ora, se tomamos o menor elemento da subárvore
direita e o posicionamos na raiz, então continua
valendo a definição.

Prof. Luciana R. Cardoso

Árvores Binárias
36

 Sabemos que o menor elemento numa árvore binária


encontra-se no nodo que ocupa a posição mais à
esquerda possível. Este nodo certamente não terá um
filho esquerdo pois se o tivesse não seria o menor da
árvore. Pode, entretanto, ter um filho direito (maior ou
igual).
 Primeiro, desenvolveremos uma função que procura na
árvore o nodo que armazena o menor elemento, desliga o
nodo da árvore e retorna um ponteiro para o mesmo.
Lembrando que se este nodo tiver um filho direito, este
último tomará o lugar do pai.

Prof. Luciana R. Cardoso

18
03/06/2018

Árvores Binárias
37

//retira nodo menor da arvore


NodoArvore* tiraMenor(ArvoreBin& arv){
NodoArvore* pAux;
if(arvVazia(arv->subArvEsq)) {
pAux = arv;
arv=arv->subArvDir;
return pAux;
}
return tiraMenor(arv->subArvEsq);
};

Prof. Luciana R. Cardoso

Árvores Binárias
38

NodoArvore* tiraMaior(ArvoreBin& arv){


NodoArvore* pAux;
if(arvVazia(arv->subArvDir)){
pAux = arv;
arv=arv->subArvDir;
return pAux;
}
return tiraMaior(arv->subArvDir);
};

Prof. Luciana R. Cardoso

19
03/06/2018

Árvores Binárias
39

 A seguir mostramos a função em C++ para realizar a


remoção de um elemento da árvore. Sempre
testamos se a raiz da árvore é o elemento a ser
removido. Se for, basta abranger as três
possibilidades mostradas em slide anterior.
Senão, chamamos a mesma função recursivamente,
até encontrar o elemento como sendo raiz de alguma
subárvore.

Prof. Luciana R. Cardoso

Árvores Binárias
40

//remove um elemento da arvore conforme o elemento digitado


//para realizar a remoção de um elemento da árvore
NodoArvore* remove(ArvoreBin& arv, int elem) {
NodoArvore* pn;
int aux;
if ( arvVazia(arv) ){
cout << "\nArvore Vazia ou Elemento nao existe na arvore.\n")
return NULL;
}
if ( arv->info == elem ) {
pn = arv;
if ( arvVazia(arv->subArvEsq) )
arv = arv->subArvDir;
else if ( arvVazia(arv->subArvDir) )
arv =arv->subArvEsq;

Prof. Luciana R. Cardoso

20
03/06/2018

Árvores Binárias
41
else { // há dois filhos
pn = tiraMenor( arv->subArvDir );
// Trocando a info da raiz com o nodo retirado
aux = arv->info;
arv->info = pn->info;
pn->info = aux;
}
cout <<"\nElemento “ << pn->info <<"foi removido com sucesso. ";
return pn;
}
else {
if (elem < arv->info)
return remove(arv->subArvEsq, elem);
else
return remove(arv->subArvDir, elem);
cout << "\nArvore Vazia ou Elemento nao existe na arvore.\n”;
}
}

Prof. Luciana R. Cardoso

Árvores Binárias
42

 Na função para localizar um elemento, se este não se encontra na raiz


da árvore, procurá-lo-emos na subárvore esquerda caso ele seja menor
que a raiz ou na subárvore direita caso contrário.
/*se este não se encontra na raiz da árvore, procurá-lo-emos na subárvore
esquerda caso ele seja menor que a raiz ou na subárvore direita caso contrário*/
NodoArvore* localiza(ArvoreBin arv, char elem){
if(arvVazia(arv))
return NULL;
if(arv->info == elem)
return arv;
if(elem < arv->info)
return (localiza(arv->subArvEsq, elem));
else
return (localiza(arv->subArvDir, elem));
};

Prof. Luciana R. Cardoso

21
03/06/2018

Árvores Binárias
43

Um pequeno programa para exemplificar o uso.


int main() {
ArvoreBin arvore;
NodoArvore* pn;
fazArvVazia(arvore);
insere(arvore,criaNodo('j'));
insere(arvore,criaNodo('p'));
insere(arvore,criaNodo('k'));
insere(arvore,criaNodo('a'));
insere(arvore,criaNodo('b'));
insere(arvore,criaNodo('s'));
cout << "Em ordem:\n";
impEmOrdem(arvore);
cout << "\nPre ordem:\n";
impPreOrdem(arvore);
cout << "\nPos ordem:\n";
impPosOrdem(arvore);
cout << endl;
Prof. Luciana R. Cardoso

Árvores Binárias
44

pn = localiza(arvore,'p');
if ( pn )
cout << "Elemento localizado: \n“ << pn>info;
else
cout << " Elemento nao localizado\n”;
pn = localiza(arvore,'q');
if (pn)
cout << " Elemento localizado: \n“ << pn->info;
else
cout << " Elemento nao localizado\n“;
pn = remove(arvore,'p');
destroiNodo(pn);
impEmOrdem(arvore);
destroi(arvore);
}

Prof. Luciana R. Cardoso

22
03/06/2018

Exercícios
45

 1) Seja uma árvore binária de inteiros inicialmente


vazia. Esquematize o seu estado final, após terem
sido inseridos os elementos 4, 1, 0, 5, 3, 7, 2, 6, 9 e 8,
nesta ordem.
 2) Informe a ordem em que os elementos acima
inseridos serão visitados utilizando os
atravessamentos:
 em-ordem;
 pós-ordem;
 pré-ordem.

Prof. Luciana R. Cardoso

Exercícios
46
 3) Faça um algoritmo para o seguinte menu:
int tela_lista(){
printf("\xC9");
for (i=0; i<=43; i++) printf("\xCD");
printf("\xBB\n");//45
printf("\xBA Escolha sua opção: \xBA\n");
printf("\xBA __________________ \xBA\n");
printf("\xBA \xBA\n");
printf("\xBA 1 - Inserir elemento na Arvore \xBA\n");
printf("\xBA 2 - Remover elemento na Arvore \xBA\n");
printf("\xBA 3 - Localizar elemento na Arvore \xBA\n");
printf("\xBA 4 - Imprimir em Em-ordem Arvore \xBA\n");
printf("\xBA 5 - Imprimir em Pre-ordem Arvore \xBA\n");
printf("\xBA 6 - Imprimir em Pos-ordem Arvore \xBA\n");
printf("\xBA 7 - Numero de nodos nao-terminais \xBA\n");
printf("\xBA 8 - Remove maior elemento \xBA\n");
printf("\xBA 9 - Remove menor elemento \xBA\n");
printf("\xBA 10 - Imprimir altura da Arvore \xBA\n");
printf("\xBA 11 - Limpar a Arvore \xBA\n");
printf("\xBA 12 - Sair do Sistema \xBA\n");
printf("\xBA \xBA\n");
printf("\xC8");
for (i=0; i<=43; i++) printf("\xCD");
printf("\xBC\n");
printf(" Opção: ");
scanf("%d",&op);
return op;
}

Prof. Luciana R. Cardoso

23
03/06/2018

Exercícios
47

 4) Faca uma função em C++ para retornar o


numero de nodos não-terminais numa árvore
binária.
 5) Escrever uma função em C++ que retorne
a altura de uma arvore binária.
 6) Faca uma função em C++ que retire o
maior elemento de uma arvore de busca
binária.

Prof. Luciana R. Cardoso

Exercícios
48

 Pesquise e descreva sucintamente a estrutura


de dados Heap.
 Pesquise e descreva sucintamente arvores
digitais.
 12) Pesquise e descreva sucintamente arvore
B.

Prof. Luciana R. Cardoso

24
03/06/2018

Árvores Binárias
49

 Mostrar o código

Prof. Luciana R. Cardoso

25

Você também pode gostar