Está en la página 1de 12

rvores binrias

Este um resumo de parte das sees 5.4 (Trees, p.216), 5.6 (Tree traversal, p.230), 5.7 (Recursive binarytree algorithms, p.235) e 12.5 (Binary Search Trees) do livro do Sedgewick.

Definio
Uma rvore binria (= binary tree) formada de ns; cada n tem um certo contedo (por exemplo, um nmero inteiro) e os endereos (das razes) de duas subrvores: uma esquerda e uma direita. Eis um exemplo de n:
typedef struct node *link; struct node { int item; link l, r; } ; // contedo do n // 'l' de "left" e 'r' de "right"

Termos tcnicos importantes: raiz de uma rvore, filho de um n, pai de um n, folha de uma rvore, n interno de uma rvore, nvel de um n. Em geral, quando dizemos "um n x" devemos entender que x o endereo de um n. Nesses termos, o filho esquerdo de um n x x->l e o filho direito x->r. Um n x uma folha se no tem filhos, ou seja, se x->l e x->r valem NULL. Para ilustrar o conceito de rvore, eis uma pequena funo (veja programa 5.17, p.236, do Sedgewick) que calcula o nmero de ns de uma rvore binria.
// Esta funo devolve o nmero de ns // da rvore binria cuja raiz h. int count(link h) { if (h == NULL) return 0; return count(h->l) + count(h->r) + 1; }

Exerccios
1. [Sedg 5.59, p.225] Escreva uma funo recursiva que receba uma rvore binria ab e um nmero x e remova da rvore todas as folhas que tenham item igual a x.

Altura de um n e altura de uma rvore


A altura (= height) de um n h em uma rvore binria a "distncia" entre h e o seu descendente mais afastado. Mas precisamente, a altura de h o nmero de links no mais longo caminho que leva de h at uma folha. Os caminhos a que essa definio se refere so os obtido pela iterao dos comandos x = x->l e x = x->r, em qualquer ordem. Exemplo: a altura de uma folha 0. A funo abaixo (veja programa 5.17, p.236, do Sedgewick) calcula a altura de um n h. Ela d uma resposta razovel at mesmo quando h NULL.

// Devolve o altura de um n h em uma rvore binria. int height(link h) { int u, v; if (h == NULL) return -1; u = height(h->l); v = height(h->r); if (u > v) return u+1; else return v+1; }

A altura de uma rvore a altura de sua raiz. A altura de uma rvore com N ns pode variar de lg(N) at N-1. (Como de hbito, lg uma abreviatura de log2.)

Exerccios
2. Escreva uma funo no-recursiva que calcule o nmero de ns de uma rvore binria. 3. Mostre que toda rvore binria com N ns tem altura maior ou igual ao piso de lg(N). 4. [Profundidade] A profundidade (= depth) de um n x em uma rvore binria com raiz h a "distncia" x e h. Mais precisamente, a profundidade de x o comprimento do (nico) caminho que vai de h at x. Por exemplo, a profundidade de h 0 e a profundidade de h->l 1. Escreva uma funo que determine a profundidade de um n dado em relao raiz da rvore. 5. Suponha que cada n da rvore tem um campo depth do tipo int. Preencha o campo de cada n com a altura do n. 6. [Cdigos de ns] Um caminho que vai da raiz de uma rvore at um n pode ser representado por uma seqncia de 0s e 1s: toda vez que o caminho "desce para a esquerda" temos um 0; toda vez que "desce para a direita" temos um 1. Diremos que essa seqncia de 0s e 1s o cdigo do n. Suponha agora que todo n de nossa rvore tem um campo adicional cod capaz de armazenar uma cadeia de caracteres. Escreva uma funo que preencha o campo cod de cada n com o cdigo do n. 7. [Reconstruo] Suponha dados os cdigos de todas as folhas de uma rvore binria. Escreva uma funo que reconstrua a rvore a partir desses cdigos das folhas.

Como percorrer uma rvore


O seguinte exemplo uma verso simplificada do programa 5.14, p.231, do Sedgewick. Ele imprime o item de cada n da rvore binria cuja raiz h (o "h" inicial de "here").
// Imprime o item de cada n de uma rvore binria h, // que tem ns do tipo node. void imprime (link h) { if (h == NULL) return; printf("%d\n", h->item); imprime(h->l); imprime(h->r); }

Essa funo percorre a rvore em ordem raiz-esquerda-direita (= preorder). Se as trs ltimas instrues forem trocadas por
imprime(h->l); printf("%d\n", h->item); imprime(h->r);

a rvore ser percorrida em ordem esquerda-raiz-direita (= inorder). Se as trs ltimas instrues forem trocadas por
imprime(h->l); imprime(h->r); printf("%d\n", h->item);

a rvore ser percorrida em ordem esquerda-direita-raiz (= postorder).

Exerccios
8. Escreva uma funo que encontre o n de uma rvore binria cujo item tem um dado valor. 9. [Sedg 5.86] Escreva uma funo que calcule o nmero de folhas de uma rvore binria. Faa trs verses: uma que percorra a rvore em inorder, outra que percorra a rvore em preorder e outra que percorra a rvore em postorder.

Verso no-recursiva dos algoritmos de percurso


Abaixo temos uma verso simplificada do programa 5.15, p.233, de Sedgewick. Ela recebe uma rvore h no-vazia (ou seja, h != NULL) e imprime o contedo de cada n. Nossa soluo usa as funes de manipulao de pilha que discutimos em outro captulo. Ela supe que a rvore no vazia e tem 100 ns ou menos; na verdade, basta apenas que a altura da rvore no passe de 100.
// Imprime o item de cada n de uma rvore binria h. // A funo supe que h != NULL e que a altura da rvore // no passa de 100. // void imprime_red (link h) { STACKinit(100); STACKpush(h); while (!STACKempty()) { h = STACKpop(); printf("%d\n", h->item); if (h->r != NULL) STACKpush(h->r); if (h->l != NULL) STACKpush(h->l); } }

Esta funo percorre a rvore na ordem raiz-esquerda-direita, ou seja, em preorder. Ela usa uma pilha de ns (todos diferentes de NULL) para gerenciar o andamento do algoritmo. Todo n x na pilha representa o comando "imprima os ns da rvore cuja raiz x". No cdigo abaixo, a pilha implementada em um vetor pilha[0..t], sendo t o ndice do topo da pilha:

// Imprime o item de cada n de uma rvore binria h. // A funo supe que h != NULL. void imprime_red (link h) { link *pilha; int t; pilha = malloc((1+height(h)) * sizeof (link)) pilha[t=0] = h; while (t >= 0) { h = pilha[t--]; printf("%d\n", h->item); if (h->r != NULL) pilha[++t] = h->r; if (h->l != NULL) pilha[++t] = h->l; } free(pilha); }

Note que pilha[i] != NULL para todo i entre 0 e t.

Exerccios
10. [Inorder no-recursivo. Sedg 5.82, p.235] Escreva uma verso iterativa do imprime que percorra a rvore na ordem esquerda-raiz-direita (= inorder). 11. [Postorder no-recursivo. Sedg 5.83, p.235] Escreva uma verso iterativa do imprime que percorra a rvore na ordem esquerda-direita-raiz (= postorder). (Cuidado!) 12. Escreva uma funo que calcule a soma dos contedos (campos item) dos ns de uma rvore binria. Percorra a rvore em ordem esquerda-raiz-direita (= inorder).

Percorrendo a rvore "por nveis"


Os ns podem ser percorridos em uma quarta ordem, diferente da raiz-esquerda-direita, da esquerda-raizdireita e da esquerda-direita-raiz. Para fazer isso, basta usar uma fila no lugar de uma pilha. (Veja programa 5.16, p.235, de Sedgewick.)
// Imprime o item de cada n de uma rvore binria h. // A funo supe que h != NULL. void imprime (link h) { link *fila; int i, f; fila = malloc(count(h) * sizeof (link)); fila[0] = h; i = 0; f = 1; while (f > i) { h = fila[i++]; printf("%d\n", h->item); if (h->l != NULL) fila[f++] = h->l; if (h->r != NULL) fila[f++] = h->r; } free(fila); }

A funo usa uma fila implementada em um vetor fila[i..f-1]: o ndice do primeiro da fila i e o ndice do ltimo f-1. Todos os elementos da fila so diferentes de NULL.

Desenho de uma rvore


O programa 5.18, p.237, de Sedgewick faz um desenho de uma rvore binria. A funo show supe que o item de cada n do tipo char e no do tipo int como acima.
// A funo show faz um desenho esquerda-direita-raiz // da rvore x. O desenho ter uma margem esquerda de // 3b espaos. void show(link x, int b) { if (x == NULL) { printnode('*', b); return; } show(x->r, b+1); printnode(x->item, b); show(x->l, b+1); }

// A funo auxiliar printnode imprime o caracter // c precedido de 3b espaos e seguido de uma mudana // de linha. void printnode(char c, int b) { int i; for (i = 0; i < b; i++) printf(" printf("%c\n", c); }

");

Eis uma amostra do resultado de show(x,0). Troquei os espaos em branco por "-" para facilitar a leitura.
------* ---H ------------* ---------G ------------* ------F ---------* E ------* ---D ------------* ---------C ------------* ------B ------------* ---------A ------------*

Eis o resultado da impresso da mesma rvore em ordem raiz-esquerda-direita. Troquei os espaos em branco por "-" para facilitar a leitura.

E ---D ------B ---------A ------------* ------------* ---------C ------------* ------------* ------* ---H ------F ---------* ---------G ------------* ------------* ------*

Para obter isso, troque os trs ltimos comandos de show por


printnode(x->item, b); show(x->r, b+1); show(x->l, b+1);

Construo de um torneio
O programa 5.19, p.238, de Sedgewick ilustra a construo de uma rvore binria. Diremos que uma rvore binria um torneio se cada n que no seja uma folha contm uma cpia do maior dos items de seus dois filhos.
// A funo max recebe um vetor no-vazio a[p..q] // (portanto p <= q) e constroi um torneio cujas folhas // so a[p],..,a[q]. A funo devolve a raiz do torneio. link max(int a[], int p, int q) { int m, u, v; link x; m = (p + q) / 2; x = malloc(sizeof *x); if (p == q) { x->l = x->r = NULL; x->item = a[m]; return x; } x->l = max(a, p, m); x->r = max(a, m+1, q); u = x->l->item; v = x->r->item; if (u > v) x->item = u; else x->item = v; return x; }

Compare com o programa 5.6 de Sedgewick.

Exerccios

13. Aplique a funo max acima ao vetor 1 2 3 4 5 . 14. [Sedg 5.91, p.241] Escreva uma funo recursiva que remova de um torneio todas as folhas que contenham uma dada chave. (Veja acima o exerccio Sedg 5.59.) 15. [Busca binria] Escreva uma funo que contrua a rvore binria que representa todas as possveis buscas binrias em um vetor crescente a[p..r]. Cada n da rvore dever conter o ndice do vetor envolvido em uma comparao com a chave procurada. 16. Escreva uma funo que construa uma rvore binria aleatria com n ns e chaves aleatrias.

rvore de expresso aritmtica


Vamos considerar aqui mais um exemplo de construo de rvore binria. Desta vez, a rvore ser uma representao de uma expresso aritmtica. Suponha que temos uma expresso aritmtica cujos operadores so todos binrios. Mais concretamente, suponha que os operadores so soma (+) e multiplicao (*). Suponha tambm, para simplificar, que os operandos so nomes de variveis, cada um consistindo de uma nica letra. Uma expresso aritmtica pode ser muito bem representada por uma rvore binria: as folhas da rvore so operandos e os ns internos so operadores.
* / + / a / * \ * \ + / \ / \ b c d e \ f

Se a rvore for lida em ordem esquerda-raiz-direita, teremos a expresso em notao infixa. Se for lida em ordem esquerda-direita-raiz, teremos a expresso em notao posfixa. Se for lida em ordem raiz-esquerdadireita-raiz, teremos a expresso em notao prefixa. infixa
(a+(b*c)*(d+e))*f

posfixa abc*de+*+f* prefixa *+a**bc+def O programa 5.20, p.240, de Sedgewick, faz o servio inverso: transforma a expresso prefixa (no-vazia, claro) em uma rvore binria. Se a expresso consiste em um nica letra, a rvore ter um nico n; se a expresso for algo como *ab, a rvore ter uma raiz e duas folhas. Suponha que a expresso prefixa est armazenada em um vetor global de caracteres a[i..] , sendo i uma varivel global.

typedef struct Tnode *link; struct Tnode { char token; link l, r; } ; char *a; int i; // // // // // A funo parse atua sobre a expresso prefixa a[i..]. Os operadores so '+' e '*', cada varivel tem um s caracter, e no h espaos entre os caracteres. A funo transforma a expresso em uma rvore binria e devolve a raiz da rvore.

link parse() { char t; link x; t = a[i++]; x = malloc(sizeof *x); x->token = t; if (t == '+' || t == '*') { x->l = parse(); x->r = parse(); } else x->l = x->r = NULL; return x; }

Exerccios
17. Escreva uma funo que calcule o valor da expresso aritmtica representada por uma rvore sendo dodos os valores das variveis. Suponha que os valores das variveis so dados em um vetor do tipo int indexado por letras. 18. Escreva uma funo que receba uma expresso aritmtica em notao infixa e construa a correspondente rvore. Suponha que a expresso s envolve os operadores '+' e '*' e operandos que consistem em uma s letra. A pgina sobre pilhas pode ser til.

Valor de uma expresso aritmtica


O captulo 14 do livro de Roberts discute a implementao de processador de expresses aritmticas. Vamos examinar aqui apenas a estrutura das rvores que representam expresses (mais rica e completa que aquela usada acima) e as funes que calculam o valor de uma expresso. Nossas expresses aritmticas admitem os operadores =, +, -, *, / e admitem operandos que podem ser nmeros inteiros, nomes de variveis e, claro, sub-expresses. Exemplo:
= / \ y 3 * / \ + / \ - med / \ 6 var

Eis a declarao do tipo de uma expresso:


typedef char *string; // Type: expression // ---------------// This type is used to represent the abstract notion of an // expression, such as one you might encounter in a C program. // An expression is defined recursively to be one of the // following: // 1. A constant // 2. A string representing the name of a variable // 3. Two expressions combined by an operator // typedef struct node *expression // Type: exptype // ------------// This enumeration type is used to differentiate the three // expression types: constants, variables, and subexpressions. // typedef enum {Constant, Variable, Subexpression} exptype;

Para representar os ns da rvore vamos usar uma estrutura que envolve um union:
// Type: node // ---------// An expression is represented as tree. The contents of each // node consists of a tagged union that allows the node to // have multiple representations [interpretations?]. // struct node { exptype type; union { int constRep; // a constant string varRep; // name of a variable struct { char op; // '=' or '+' or '-' or '*' ou '/' expression lhs; // left subexpression expression rhs; // right subexpression } subexpRep; } contents; } ;

Finalmente, eis as funes que calculam o valor de uma expresso:


// Function: EvalExp // Usage: value = EvalExp(exp); // --------------------------// Returns the value of the expression exp. (The function // assumes that the values of all variables have been // already loaded into the appropriate table.) // int EvalExp(expression exp) { switch (exp->type) { case Constant: return exp->contents.constRep; case Variable: return GetVariableValue(exp->contents.varRep); case Subexpression: return EvalSubExp(exp); } }

// Returns the value of the subexpression exp. (The values // of all variables must have been already loaded into the // appropriate table.) // static int EvalSubExp(expression exp) { char op; expression leftexp, rightexp; int leftval, rightval; op = exp->contents.subexpRep.op; leftexp = exp->contents.subexpRep.lhs; rightexp = exp->contents.subexpRep.rhs; if (op = '=') { rightval = EvalExp(rightexp); SetVariableValue(leftexp->contents.varRep, rightval); return rightval; } leftval = EvalExp(leftexp); rightval = EvalExp(rightexp); switch (op) { case '+': return leftval + rightval; case '-': return leftval - rightval; case '*': return leftval * rightval; case '/': return leftval / rightval; } } // Prototypes of auxiliary functions: // Returns the value of variable var. int GetVariableValue(string var) ; // Sets the value of variable var to val. int SetVariableValue(string var, int val) ;

Mais exerccios
19. Escreva uma funo que receba um vetor a[1..n], interprete esse vetor como um heap, e construa a correspondente rvore binria. 20. [Sedg 12.54, p.511] O comprimento interno de uma rvore binria a soma dos comprimentos dos caminhos que levam da raiz a cada uma das folhas. Escreva um programa recursivo que calcule o comprimento interno de uma rvore binria dada. 21. [Sedg 12.63, p.514, ndices no lugar de ponteitos] rvores binrias podem ser implementadas com ndices no lugar de ponteiros, da seguinte maneira: teremos trs vetores "paralelos", item[1..N], l[1..N] e r[1..N]; para cada ndice i, l[i] o ndice do filho esquerdo de i e r[i] o ndice do filho direito. Exerccio: escreva todas as funes desse captulo para a implementao que acabamos de sugerir. [Essa implementao tem suas vantagens porque reduz o tempo consumido pelas sucessivas chamadas de malloc durante a construo da rvore. Mas exige que o nmero total de ns seja conhecidono antes que a rvore comece a ser construda.]

Compresso de arquivos
[Esse material est no capitulo 22 da 2-a edio do livro de Sedgewick.] Suponha dada uma cadeia de caracteres, digamos
bafeabacaadefa

Cada caracter representado por 8 bits: smbolo caracter grfico ASCII a 97 b 98 c 99 d 100 e 101 f 102 bits
01100001 01100010 01100011 01100100 01100101 01100110

Portanto, nossa cadeia de caracteres representada pela seguinte cadeia de bits:


011000100110000101100110011001010110000101100010011000010110001101100001011000010110010 0011001010110011001100001

Suponha agora que adotemos uma codificao com nmero varivel de bits poucos bits para as letras mais freqentes e muitos bits para letras raras: smbolo bits grfico
a b c d e f 0 101 100 111 1101 1100

Agora podemos representar a cadeia bafeabacaadefa por uma cadeia de bits bastante curta:
1010110011010101010000111110111000

Note que no temos separadores entre as subcadeias de bits que representam os vrios caracteres. Apesar disso, a cadeia de bits pode ser decodificada sem ambigidades. Essa uma propriedade interessante e valiosa de nossa tabela de cdigos. A propriedade decorre do seguinte fato: o cdigo pode ser representado por uma rvore cujas folhas so os caracteres:

. / a / . / \ c b f \ . \ . / \ . / \ e d

Para determinar o cdigo de um caracter x, comece na raiz e caminhe at x; toda vez que descer para a esquerda, acrescente um 0 ao cdigo de x; toda vez que descer para a direita, acrescente um 1. [Veja exerccio sobre cdigos de ns]. PROBLEMA: Dada uma cadeia de caracteres, construir uma tabela de codificao que codifique a cadeia de caracteres usando o menor nmero possvel de bits. Eis um algoritmo que resolve o problema. Suponha que cada caracter x ocorre f(x) vezes na cadeia de caracteres. Ento o seguinte algoritmo produz uma codificao tima: construa uma rvore binria cujas chaves so nmeros inteiros; comece com um n para cada caracter x, sendo f(x) a chave do n; seja x um n que tem chave mnima; seja y um n que tem a segunda menor chave; faa com que x e y sejam os filhos de um novo n z; a chave do novo n ser f(x)+f(y); os ns x e y "saem do jogo" e o n z "entra no jogo"; repita o processo at que todas as subrvores se juntem. A rvore resultante conhecida como rvore de Huffman da cadeia de caracteres original. Exemplo: Suponha que nossa cadeia s contm os caracteres a, b, c, d, e, f. Suponha que o nmero de ocorrncias de cada caracter dado pela tabela: x f(x)
a b c d e f

45 13 12 16 9 5

Aplique o algoritmo. Verifique que a rvore exatamente aquela da figura acima. Exerccio: Escreva uma funo que receba uma cadeia de caracteres e construa uma rvore de Huffman para essa cadeia. Veja tambm o exerccio sobre reconstruo da rvore de cdigos. Esse material sobre codificao e compresso de arquivos pode ser encontrado no captulo 22 da 2-a edio do livro do Sedgewick. Tambm pode ser encontrado no livro Introduction to Algorithms de Cormen, Leiserson, Rivest e Stein (h uma edio do livro em portugus).

También podría gustarte