Nesta aula vamos estudar árvores binárias enquanto estrutura de dados, métodos de travessia de árvores, e procura em árvores binárias de pesquisa.
Uma árvore binária (de pesquisa) tem a seguinte estrutura:
No caso das árvores de pesquisa binária, como no exemplo acima, os nós na sub-árvore esquerda tem chaves menores ou iguais que a raiz, e os nós na sub-árvore direita tem chaves maiores ou iguais que a raiz.
Implementação: estrutura de dados
#include <stdlib.h>
#include "Item.h"
typedef struct node* link;
struct node { Item item; link l, r;};
static link head;
void init() {
head = NULL;
}
link NEW(Item item, link l, link r) {
link x = malloc(sizeof(struct node));
x->item = item;
x->l = l;
x->r = r;
return x;
}Pesquisa
Dado um elemento, a pesquisa começa na raiz (ou head ou root) e verificamos se o mesmo é igual ao item guardado na raiz. Se for igual, então retornamos o nó (link) onde estamos, caso contrário continuamos a pesquisa com o filho da esquerda ou com o filho da direita dependendo se o elemento que procuramos é menor ou maior que o item na raiz, respectivamente.
link search(link h, Item v) {
if (h == NULL)
return NULL;
if eq(v, h->item)
return h;
if less(v, h->item)
return search(h->l, v);
else
return search(h->r, v);
}Inserção
Começamos na rais e vamos percorrendo a árvore de cima para baixo até encontrar o lugar onde introduzir o novo elemento. Quando encontrarmos o lugar vazio (NULL) para o novo elemento (respeitando as regras da binary search tree), criamos um novo elemento. Tal como no caso anterior, podemos fazer esta operação de forma recursiva.
link insert(link h, Item item) {
if (h == NULL)
return NEW(item, NULL, NULL);
if (less(v, item))
h->l = insert(h->l, item);
else
h->r = insert(h->r, item);
return h;
}Exemplo de utilização por parte do cliente:
head = insert(head,10);
Menor e maior elementos
Como encontrar o menor elemento abaixo de um determinado nó? Descemos na árvore sempre pelo filho da esquerda até encontrarmos um nó sem filho esquerdo.
Como encontrar o maior elemento abaixo de um determinado nó? Descemos na árvore sempre pelo filho da direita até encontrarmos um nó sem filho direito.
link max(link h) {
if (h==NULL || h->r==NULL) return h;
else return max(h->r);
}
link min(link h) {
if (h==NULL || h->l==NULL) return h;
else return min(h->l);
}Ou em alternativa:
link max(link h) {
while(h!=NULL && h->r!=NULL)
h=h->r;
return h;
}
link min(link h) {
while(h!=NULL && h->l!=NULL)
h=h->l;
return h;
}Remoção
Operação mais complexa, três casos:
- Se o nó não tiver filhos é fácil, basta apagar.
- Se o nó tiver um só filho, também é fácil, por exemplo, para apagar o 18 no exemplo acima, redirecionamos o filho direito do 12 para o 19 e apagamos o 18.
- Remoção de um nodo interno: i) substituímos o elemento a remover pelo maior dos elementos à esquerda do elemento a ser removido; ii) removemos esse elemento (que está numa nas condições 1 ou 2).
link delete(link h, Item item) {
link aux ;
if (h==NULL) return h;
else if (less(item, h->item)) h->l=delete(h->l,item);
else if (less(h->item, item)) h->r=delete(h->r,item) ;
else{
if (h->l !=NULL && h->r !=NULL ) { /*caso 3*/
aux=max(h->l);
h->item = aux->item;
h->l=delete(h->l, aux->item);
} else { /*casos 1 e 2*/
aux=h;
if ( h->l == NULL && h->r == NULL ) h=NULL;
else if (h->l==NULL) h=h->r;
else h=h->l;
deleteItem(aux->item);
free(aux);
}
}
return h;
}Exemplo relativo ao caso 3:

Número de elementos e profundidade
int count(link h) {
if (h==NULL) return 0;
else return count(h->r)+count(h->l)+1;
}
int height(link h)
{
int u, v;
if (h == NULL) return 0;
u = height(h->l);
v = height(h->r);
if (u > v) return u+1;
else return v+1;
}Travessia
Função visit:
/* exemplo para inteiros */
void visit (Item i)
{
printf("%d\n", i);
} Travessia em Pre-Order: visita raíz antes dos filhos. Sequência para o exemplo acima:
20 12 8 2 9 18 19 32 23 45
Implementação:
void traverse(link h)
{
if (h == NULL)
return;
visit(h->item);
traverse(h->l);
traverse(h->r);
}Travessia em In-Order: visita a raíz depois do filho esquerdo e antes do direito. Sequência para o exemplo acima:
2 8 9 12 18 19 20 23 32 45
Notar que a sequência vem ordenada.
Implementação:
void traverse(link h)
{
if (h == NULL)
return;
traverse(h->l);
visit(h);
traverse(h->r);
}Travessia em Post-Order: visita a raíz depois dos filhos. Sequência para o exemplo acima:
2 9 8 19 18 12 23 45 32 20
Exemplo de aplicação: avaliação de expressões posfixadas.
Implementação:
void traverse(link h)
{
if (h == NULL)
return;
traverse(h->l);
traverse(h->r);
visit(h);
}Análise e complexidade
- Geralmente eficiente: $O(\log n)$.
- No pior caso, $O(n)$ para uma árvore desequilibrada. Exemplo, ordem de inserção: 1,2,3,4,5,6,7,8
- No caso de chaves aleatórias, $O(\log n)$.
- Comparação com pesquisa binária em tabelas:
- tempo de pesquisa comparável;
- tempo de inserção muito mais rápido.
- Tempo de pesquisa e inserção são $O(n)$ no pior caso (árvore degenerada).
Árvores equilibradas
- Evitam o pior caso de $O(n)$.
- Algum overhead na construção.
- Alternativa:
- reequilibrar uma árvore, depois de construída;
- usar aleatoriedade;
- usar técnicas especiais de construção (AVL, Red-Black, etc.).
Exemplo
Crie um programa que permita guardar, numa árvore binária, todos os alunos de IAED, onde cada aluno é caracterizado pelo seu nome, nota final e um inteiro identificativo do curso. Estruture o seu programa através de ADTs. A chave (a key) utilizada para a organização da árvore deverá ser dada pelo nome do aluno.
Cada nó da árvore binária deverá ser representado por uma estrutura do tipo:
/* O tipo Item ficara definido em Item.h */
typedef struct node {
Item item;
struct node *l;
struct node *r;
} *link;Ficheiro Item.h:
#ifndef _ITEM_
#define _ITEM_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define key(a) (a != NULL ? a->nome : "")
#define less(a,b) (strcmp(a,b)<0)
#define eq(a,b) (strcmp(a,b)==0)
#define NULLitem NULL
typedef char* Key;
typedef struct estudante {
char* nome; int notaIAED; int curso;
} * Item;
Item newItem(char*nome, int nota, int curso);
void deleteItem(Item a);
void visitItem(Item a);
#endifFicheiro Item.c:
#include "Item.h"
Item newItem(char*nome, int nota, int curso)
{
Item x = (Item)malloc(sizeof(struct estudante));
x->nome = strdup(nome);
x->notaIAED = nota;
x->curso = curso;
return x;
}
void deleteItem(Item a)
{
free(a->nome);
free(a);
}
void visitItem(Item a)
{
printf("nome: %s\n",a->nome);
printf("notaIAED: %d\n",a->notaIAED);
printf("curso: %d\n\n",a->curso);
}Interface do ADT Tabela de Símbolos, ficheiro ST.h:
#ifndef _ST_
#define _ST_
#include <stdlib.h>
#include <stdio.h>
#include "Item.h"
typedef struct STnode* link;
struct STnode { Item item; link l, r;};
void STinit(link*);
int STcount(link);
Item STsearch(link,Key);
void STinsert(link*,Item);
void STdelete(link*,Key);
void STsort(link,void (*visit)(Item));
void STfree(link*);
#endifFicheiro ST.c:
#include "ST.h"
link NEW(Item item, link l, link r)
{
link x = (link)malloc(sizeof(struct STnode));
x->item = item;
x->l = l;
x->r = r;
return x;
}
void STinit(link*head)
{
*head = NULL;
}
link insertR(link h, Item item) {
if (h == NULL)
return NEW(item, NULL, NULL);
if (less(key(item), key(h->item)))
h->l = insertR(h->l, item);
else
h->r = insertR(h->r, item);
return h;
}
void STinsert(link*head, Item item)
{
*head = insertR(*head, item);
}
Item searchR(link h, Key v)
{
if (h == NULL)
return NULLitem;
if (eq(v, key(h->item)))
return h->item;
if (less(v, key(h->item)))
return searchR(h->l, v);
else
return searchR(h->r, v);
}
Item STsearch(link head, Key v)
{
return searchR(head, v);
}
link deleteR(link h, Key k)
{
if (h==NULL) return h;
else if (less(k, key(h->item))) h->l=deleteR(h->l,k);
else if (less(key(h->item), k)) h->r=deleteR(h->r,k) ;
else {
if (h->l !=NULL && h->r !=NULL){/*caso 3*/
link aux=max(h->l);
{Item x; x=h->item; h->item=aux->item; aux->item=x; }
h->l= deleteR(h->l, key(aux->item));
}
else { /*casos 1 e 2*/
link aux=h;
if ( h->l == NULL && h->r == NULL ) h=NULL;
else if (h->l==NULL) h=h->r;
else h=h->l;
deleteItem(aux->item);
free(aux);
}
}
return h;
}
link max(link h) {
if (h==NULL || h->r==NULL) return h;
else return max(h->r);
}
int count(link h){
if (h==NULL) return 0;
else return count(h->r)+count(h->l)+1;
}
int STcount(link head){
return count(head);
}
void STdelete(link*head, Key k){
*head = deleteR(*head, k);
}
void sortR(link h, void (*visit)(Item))
{
if (h == NULL)
return;
sortR(h->l, visit);
visit(h->item);
sortR(h->r, visit);
}
void STsort(link head, void (*visit)(Item))
{
sortR(head, visit);
}
link freeR(link h)
{
if (h==NULL)
return h;
h->l=freeR(h->l);
h->r=freeR(h->r);
return deleteR(h,key(h->item));
}
void STfree(link*head)
{
*head=freeR(*head);
}Cliente, ficheiro main.c:
#include <stdio.h>
#include "ST.h"
int main(){
link turma1;
STinit(&turma1);
STinsert(&turma1, newItem("Mikhail Gorbachev", 10, 0));
STinsert(&turma1, newItem("Jimmy Carter", 10, 0));
STinsert(&turma1, newItem("Barack Obama", 12, 0));
STsort(turma1,visitItem);
printf("numero de alunos: %d\n",STcount(turma1));
STdelete(&turma1,"Barack Obama");
printf("numero de alunos: %d\n",STcount(turma1));
STfree(&turma1);
return 0;
}Função tsearch
Existem funções definidas em search.h que nos permitem gerir uma árvore binária de pesquisa.
#include <search.h>
void *tsearch(const void *key, void **rootp,
int (*compar)(const void *, const void *));
void *tfind(const void *key, const void **rootp,
int (*compar)(const void *, const void *));
void *tdelete(const void *key, void **rootp,
int (*compar)(const void *, const void *));
void twalk(const void *root, void (*action)(const void *nodep,
const VISIT which,
const int depth));
#define _GNU_SOURCE
#include <search.h>
void tdestroy(void *root, void (*free_node)(void *nodep));Nestas funções, rootp é um ponteiro para um ponteiro para a raiz e root é um ponteiro para a raiz. A árvore é vazia quando o ponteiro para a raiz for NULL. O parâmetro key é um ponteiro para o item a procurar. A função tsearch procura um elemento e, se este não existir, adiciona o mesmo. Esta função retorna um ponteiro para o elemento encontrado, ou inserido se for o caso. A função tfind é idêntica à função tsearch, mas não insere o elemento quando este não existe, retornando NULL nesse caso. A função tdelete permite eliminar um elemento da árvore. O parâmetro compar nestas três funções define a ordem total a ter em conta.
A função twalk permite efectuar travessias numa árvore binária em que o parâmetro action define o que fazer quando um nó é visitado. O parâmetro which da função action deve ser tido em conta na implementação da mesma uma vez que indica em que fase da visita estamos.
Exemplo (tsearch.c):
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <search.h>
#define STEPS 32
static void * root = NULL;
static int
compare(const void *pa, const void *pb)
{
return (long int) pa - (long int) pb;
}
void
action (const void *nodep, const VISIT which, const int depth)
{
long int data;
switch (which)
{
case preorder:
break;
case postorder:
data = *(long int *) nodep;
printf ("%6ld\n", data);
break;
case endorder:
break;
case leaf:
data = *(long int *) nodep;
printf ("%6ld\n", data);
break;
}
}
void do_nothing(void * p) {}
int
main(int argc, char *argv[])
{
long int value;
size_t i;
if (argc > 1)
srand(atoi(argv[1]));
for (i = 0; i < STEPS; i++) {
value = rand()%100;
tsearch((void *) value, &root, compare);
}
twalk(root, action);
tdestroy(root, do_nothing);
return EXIT_SUCCESS;
}Compilar e executar:
[aplf@darkstar ~]$ gcc -Wall -ansi -pedantic -o tsearch tsearch.c
[aplf@darkstar ~]$ valgrind ./tsearch $RANDOM
==21060== Memcheck, a memory error detector
==21060== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==21060== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==21060== Command: ./tsearch 17208
==21060==
1
14
19
23
25
26
29
30
34
35
37
41
48
51
52
53
57
59
62
64
66
71
76
77
85
86
97
==21060==
==21060== HEAP SUMMARY:
==21060== in use at exit: 0 bytes in 0 blocks
==21060== total heap usage: 27 allocs, 27 frees, 864 bytes allocated
==21060==
==21060== All heap blocks were freed -- no leaks are possible
==21060==
==21060== For counts of detected and suppressed errors, rerun with: -v
==21060== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
[aplf@darkstar ~]$