Capítulo IV – Árvores Gerais 4.1 – Definições e terminologia básica 4.2 – Formas de representação de árvores 4.3 – Ordenação dos nós de uma árvore 4.4 – Operadores para o TAD árvore 4.5 – Estruturas contíguas para árvores 4.6 – Estruturas encadeadas para árvores
4.5 – Estruturas Encadeadas para Árvores 4.6.1 – Estrutura encadeada direta Esquema:
Declarações: Nó: ponteiro para célula const int maxfilhos = 5; typedef celula *noh; typedef celula *arvore; struct celula { informacao info; noh Filhos[5]; }; arvore A; Nó: ponteiro para célula Árvore: ponteiro para a célula da raiz Usa muitos ponteiros desnecessários (exemplo: folhas) Limita o número de filhos A Esquema usado em árvores balanceadas (implementação de dicionários)
4.6.2 – Estrutura com listas de filhos Esquema: celula raiz 1 ncel 11 Nó: índice num vetor de células Filhos: ordenados da esquerda para a direita Estrutura das listas: encadeada, sem nó- líder
4.6.2 – Estrutura com listas de filhos celula raiz 1 ncel 11 Não necessariamente a raiz fica no nó 1 Os nós podem ficar embaralhados no vetor, sem ordenação (pré-ordem, etc.) Facilita a inserção de nós
Declarações: typedef struct arvore arvore; struct arvore { noh raiz; celula Espaco[51]; int ncel; }; noh n1, n2, n3; arvore A1, A2, A3; informacao x, y, z; const int nulo = 0; typedef int noh; typedef celfilho *lista; typedef celfilho *posição; typedef struct celfilho celfilho; struct celfilho { noh filho; celfilho *prox; }; typedef struct celula celula; struct celula { informacao info; noh pai; lista list; celula raiz 1 ncel 11
Operadores: noh FilhoEsquerdo (noh n, arvore A) { if (A.Espaco[n].list == NULL) return nulo; else return A.Espaco[n].list->filho; } informacao Elemento (noh n, arvore A) { return A.Espaco[n].info; noh raiz (arvore A) { if (A.ncel == 0) return A.raiz; celula raiz 1 ncel 11
void Esvaziar (arvore *A){ Liberar todas as células de filhos; A->raiz = nulo; A->ncel = 0; } noh Pai (noh n, arvore A) { return A.Espaco[n].pai; celula raiz 1 ncel 11
Observação: esta estrutura é muito usada para grafos noh IrmaoDireito (noh n, arvore A) { noh pai; posição idir, p; if (n == A.raiz) return nulo; pai = A.Espaco[n].pai; p = A.Espaco[pai].list; while (p->filho != n) p = p->prox; idir = p->prox; if (idir == NULL) return nulo; else return idir->filho; } celula raiz 1 ncel 11 Observação: esta estrutura é muito usada para grafos
4.6.3 – Estrutura com encadeamento de pais e irmãos Esquema: info pai fesq idir Célula
a) Declarações info pai fesq idir Célula const celula *nulo = NULL; typedef celula *noh; typedef celula *arvore; typedef struct celula celula; struct celula { informacao info; noh pai, fesq, idir; }; arvore A1, A2, A3; info pai fesq idir Célula
Exemplo ilustrativo: montagem da seguinte árvore: Foi visto em CES-10
A A B C x x E F G y x y arvore A; noh x, y; A = (noh) malloc (sizeof (celula)); A->info = 'A'; A->pai = A->idir = NULL; A->fesq = (noh) malloc (sizeof (celula)); x = A->fesq; x->info = 'B'; x->pai = A; x->fesq = (noh) malloc (sizeof (celula)); x->idir = (noh) malloc (sizeof (celula)); x->fesq->info = 'E'; x->fesq->pai = x; x->fesq->fesq = x->fesq->idir = NULL; x = x->idir; x->info = 'C'; x->pai = A; x->idir = NULL; y = x->fesq; y->info = 'F'; y->pai = x; y->fesq = NULL; y->idir = (noh) malloc (sizeof (celula)); y->idir->info = 'G'; y->idir->pai = x; y->idir->fesq = y->idir->idir = NULL; A A B C x x E F G y x y
Os comandos anteriores formam apenas esta árvore em particular Ainda neste capítulo serão vistos algoritmos gerais de formação de árvores A A B C E F G
b) Operações elementares FilhoEsquerdo (noh n, arvore A): n->fesq; IrmãoDireito (noh n, arvore: A): n->idir; Elemento (noh n, arvore A): n->info; Pai (noh n, arvore A): n->pai; Raiz (arvore A): A; info pai fesq idir Célula
c) Operador Esvaziar (&n): anula o nó n e libera todas as células que se tornam inacessíveis devido a essa anulação Por exemplo: Se n é o ponteiro A, todas as células da árvore serão liberadas Se n é o ponteiro fesq da célula que contém ‘A’, as células a serem liberadas são as de ‘B’, ‘E’, ‘C’, ‘F’, ‘G’, ‘D’, ‘H’, ‘I’, ‘J’ e ‘K’ Se n é o ponteiro idir da célula que contém ‘C’, as células a serem liberadas são as de ‘D’, ‘H’, ‘I’, ‘J’ e ‘K’ info pai fesq idir Célula Esvaziar (&A): esvazia a árvore A
Não deve ser aplicada ao ponteiro pai de nenhuma célula void Esvaziar (noh *n) { if ((*n)->fesq == NULL && (*n)->idir == NULL) free (*n); else if ((*n)->idir == NULL) { Esvaziar (&(*n)->fesq)); } else { Esvaziar (&((*n)->idir)); if ((*n)->fesq != NULL) Esvaziar (&((*n)->fesq)); *n = NULL; info pai fesq idir Célula Se o argumento for &A : A = NULL n Não deve ser aplicada ao ponteiro pai de nenhuma célula Nesta função, n guarda o endereço de um ponteiro para celula
Esvaziar A: Esvaziar B: Esvaziar C: Esvaziar D: Esvaziar H: void Esvaziar (noh *n) { if ((*n)->fesq == NULL && (*n)->idir == NULL) free (*n); else if ((*n)->idir == NULL) { Esvaziar (&((*n)->fesq)); } else { Esvaziar (&((*n)->idir)); if ((*n)->fesq != NULL) (*n) = NULL; Esvaziar A: Esvaziar B: Esvaziar C: Esvaziar D: Esvaziar H: Esvaziar I: Esvaziar J: Esvaziar K: Liberar K Liberar J Liberar I Liberar H Liberar D Esvaziar F: Esvaziar G: Liberar G Liberar F Liberar C Esvaziar E: Liberar E Liberar B Liberar A
d) arvore Criacao (informacao x, ListaArvores LA): H G F D C E B K J I ListaÁrvores
Situação no início da função Criacao (x, LA): Adota-se a estrutura contígua para a lista de árvores k x # ● 1 2 3 - - - - - - - - y - - - - - - ultimo Arvores LA a ● b ● c ● z ●
Aloca o nó-raiz da nova árvore e o preenche: x k k ● A (a ser retornado) ● ● # ● 1 2 3 ● ● ● ● - - - - - - - - y - - - - - - Arvores LA ultimo y a ● b ● c ● z ●
Procura primeira árvore não-nula em LA e a faz sub-árvore esquerda da raiz: x k k ● A (a ser retornado) ● # ● 1 2 3 ● ● ● ● - - - - - - - - y - - - - - - Arvores LA ultimo y a ● ● b ● c ● z ●
Percorre o vetor, procurando árvores não-nulas e acertando o pai e o idir de suas raizes: x k k ● A (a ser retornado) # ● 1 2 3 ● ● ● ● - - - - - - - - y - - - - - - Arvores LA ultimo y a b ● c ● z ● ● ● ● ●
arvore Criacao (informacao x, ListaArvores LA) { int i, j, ant; arvore A; A = malloc (sizeof (celula)); A->info = x; A->pai = A->fesq = A->idir = NULL; for (i = 1; i <= LA.ultimo && LA.Arvores[i] == NULL; i++); if (i <= LA.ultimo){ A->fesq = LA.Arvores[i]; for (j = i; j <= LA.ultimo; j++) if (LA.Arvores[j] != NULL) { if (j != i) LA.Arvores[ant]->idir = LA.Arvores[j]; LA.Arvores[j]->pai = A; LA.Arvores[j]->idir = NULL; ant = j; } return A; struct ListaArvores { int ultimo; arvore Arvores [51]; }
e) Formação de árvores: uma nova árvore pode ser formada pelos seguintes métodos (entre outros): Função Criação: vista no item d Leitura dos pares pais-filhos Formação interativa Leitura da forma parentética
e.1) Leitura dos pares pais-filhos: Usada para formar uma floresta O operador fornece todos os pares pais-filhos da floresta Por exemplo, seja uma floresta cujos nós das árvores guardam apenas um caractere cada Nessa floresta é proibido dois ou mais nós guardarem o mesmo caractere
O operador fornece os seguintes pares: A ordem entre dois irmãos é estabelecida pela ordem fornecida pelo operador Um filho não pode aparecer em mais de um par Não pode haver ciclos; Exemplo: Pai-Filho C-F R-S L-M R-T A-B C-G A-C N-P L-N A-D Pai-Filho A-B B-C C-D D-A
Floresta formada: Pai-Filho C-F R-S L-M R-T A-B C-G A-C N-P L-N A-D A
e.2) Formação interativa: constrói interativamente uma nova árvore, retornando-a Para o encadeamento de pais e irmãos é apresentada uma construção em largura A função elaborada será arvore NovaArvore (void) A seguir, uma saída típica para a execução dessa função
A arvore tem raiz? (s/n): s Digite a informacao da raiz: A Quantos filhos tem A? 3 1.o filho de A: B 2.o filho de A: C 3.o filho de A: D Quantos filhos tem B? 1 1.o filho de B: E Quantos filhos tem C? 2 1.o filho de C: F 2.o filho de C: G Quantos filhos tem D? 1 1.o filho de D: H Quantos filhos tem E? 0 Quantos filhos tem F? 0 Quantos filhos tem G? 0 Quantos filhos tem H? 3 1.o filho de H: I 2.o filho de H: J 3.o filho de H: K Quantos filhos tem I? 0 Quantos filhos tem J? 0 Quantos filhos tem K? 0
Perguntar pela raiz, alocando-a e colocando-a em ff: Idéia: Usar uma fila ff para guardar os nós já introduzidos na estrutura, para os quais não se indagou ainda sobre o número e o nome de seus filhos Perguntar pela raiz, alocando-a e colocando-a em ff: ff A A ●
Deletar ff, copiando sua frente no nó avulso x: ● x
Perguntar pelo número de filhos de x, guardando-o em nf: ff 3 nf A A ● x
Perguntar pelo 1º filho de x, alocando-o e colocando-o em ff: 3 nf A A ● x A alocação é a partir do filho esquerdo de x ● B ●
Perguntar pelos outros filhos de x, alocando-os e colocando-os em ff: 3 nf A A ● x A alocação é a partir do respectivo irmão esquerdo B ● C ● D ● ● ●
Deletar ff, copiando sua frente em x: 3 nf A A ● x B ● C ● D ● x
Perguntar pelo número de filhos de x, guardando-o em nf: ff nf 3 1 A A ● B ● C ● D ● x
Perguntar pelo filho de x, alocando-o e colocando-o em ff: 1 nf A A ● B C ● D ● x ● E ●
Deletar ff, copiando sua frente em x: 1 nf A A ● B C ● D ● x x E ●
Perguntar pelo número de filhos de x, guardando-o em nf: ff nf 1 2 A A ● B C ● D ● x E ●
Perguntar pelos filhos de x, alocando-os e colocando-os em ff: 2 nf A A ● B C D ● ● x E ● F ● G ● ●
Deletar ff, copiando sua frente em x: 2 nf A A ● E assim por diante B C D ● x x E ● F ● G ●
arvore NovaArvore () { arvore A; char c; fila ff; noh x, y; int nf, i; /* Verificacao da existencia de raiz */ write ("A arvore tem raiz? (s/n): "); read (c); while (c != 's' && c != 'n' && c != 'S' && c != 'N') read (c); if (c == 'n' || c == 'N') A = NULL; else { /* Leitura e armazenamento da raiz */ A = malloc (sizeof(celula)); write ("Digite a informacao da raiz: "); read (A->info); A->pai = A-> fesq = A->idir = NULL; InitFila (&ff); EntrarFila (A, &ff);
/* Leitura e armazenamento dos outros nos */ x = DeletarFila(&ff); write ("Quantos filhos tem ", x->info, "?"); read (nf); for (i = 1; i <= nf; i++) { if (i == 1) y = x->fesq = malloc(sizeof(celula)); else y = y->idir = malloc(sizeof(celula)); write (i, ".o filho de ", x->info, ":"); read (y->info); y->pai = x; y->fesq = y->idir = NULL; EntrarFila (y, &ff); } } while (FilaVazia(ff) == FALSE); } /* Fim do else */ return A; }
e.3) Leitura da forma parentética Digitar a forma, armazenando-a numa string, e analisar a cadeia, armazenando-a na estrutura com encadeamento de pais e irmãos Pode-se usar a seguinte formulação recursiva: Sendo c uma letra genérica, (c) é uma forma parentética correta Sendo α1, α2, α3, ... αn, formas parentéticas corretas (n > 0), então (c α1α2α3 ... αn) também é
Programa para somente analisar a parentética typedef char logic; const logic TRUE = 1, FALSE = 0; /* Prototipos de funcoes */ void Analisa (void); void Erro (char *); /* Variaveis globais */ char expr[100]; int i; char erro;
/* Programa principal */ int main () { write ("Forma parentetica: "); read (expr); i = 0; erro = FALSE; Analisa (); if (!erro && expr[i] != '\0') Erro("enter esperado"); if (erro) write ("\parentetica reprovada!"); else write ("parentetica valida!"); }
O programa para armazenar a árvore usa este programa como esqueleto /* Funcao Analisa: Verifica se a forma parentetica digitada estah correta */ void Analisa () { if (expr[i] != '(') {Erro ("( esperado"); return;} i++; if (isalpha (expr[i]) == FALSE) { Erro("Letra esperada"); return;} for (i++; expr[i] == '('; ) Analisa (); if (expr[i] != ')') {Erro (") esperado"); return;} } /* Funcao Erro: emite mensagem de erro */ void Erro (char *s) { erro = TRUE; write (s); O programa para armazenar a árvore usa este programa como esqueleto Fica como exercício