Você está na página 1de 33

1

rvores Binrias e Mtodos e Busca


1. rvores Binrias So estruturas de dados que contm uma quantidade finita de elementos chamados de ns, que est vazia ou particionado em 3 subconjuntos: - 1 subconjunto Raiz da rvore - 2 e 3 subconjunto subrvores esquerda e direita Ex.: B D G E A C F

H I G Cada n na rvore tem no mximo 2 ns descendentes (1 esquerdo e 1 direito), onde esses dois ns descendentes so chamados de ns irmos. Nenhum n descendente pode ter algum dos seus descendentes como sendo algum ancestral. Tambm, nenhum n na rvore pode ter como pai, 2 ancestrais, ou melhor, nenhuma ns apontado por 2 outros ns ao mesmo tempo. Abaixo, temos exemplos de rvores que no so binrias: (a) B D G E H G A C F I D G (b) B E H G A C F I

(c) B D G E

A C F H G I

2 1.1. rvores Estritamente Binria So rvores onde todos os ns no folhas, tm os dois descendentes, esquerda e direita: Ex.: B D E F H G A C G I

Nvel de profundidade relativo com o local onde os ns se encontram na rvore. O n raiz se encontra no nvel 0, os descendentes esquerda e direita do n raiz, se encontram no nvel 1 e assim sucessivamente at as folhas. A profundidade da rvore igual ao ltimo nvel. Se o ltimo nvel o 3, isso quer dizer que a rvore possui profundidade 3. 1.2. rvores Binrias Completas So rvores estritamente binrias, onde todos os ns considerados folhas, esto no mesmo nvel de profundidade. As rvores binrias completas seguem as seguintes regras: 2d -> Sendo d o nvel de profundidade, temos com isso o nmero de elementos em cada nvel. 2d 1 -> Ns que no possuem folhas. tn = 2d+1 1 -> Total de ns em uma rvore binria completa. Ex.: A

D H G I J G

E K L G

F M N G

G O

1.3. rvores Binrias Quase Completas Uma rvore binria quase completa segue as seguintes regras:

3 1) Cada folha deve estar no nvel d ou no nvel d-1 2) Para qualquer n ND da rvore com um descendente direito no nvel d, todos os descendentes esquerdos de ND que so folhas, deve tambm encontrar-se em d. 3) Os ns de uma rvore binria so organizados por sua cardinalidade, sendo representados em um vetor. Seguem as regras: i. O n raiz, tem cardinalidade 1 e representado no vetor, pelo ndice 0. ii. Os ns da esquerda de qualquer n tm cardinalidade 2p, onde p a cardinalidade do pai, e no vetor, representado pelo ndice: 2p + 1. iii. Os ns da direita de qualquer n tm cardinalidade 2p + 1, onde p a cardinalidade do pai, e no vetor, representado pelo ndice 2p + 2. As rvores binrias quase completas, quando representadas em vetores, no possuem lacunas entre o n raiz e o ltimo n na rvore mais a direita. Ex.: (a) A (b) A

B G D E I 0 1 J G 2 ... F

D H G 0 1 I J G

E K

2 ... J

10 K

H G

A B C D E F G H I

9 J

A B C D E F G H I

1.4. Percorrendo rvores binrias Existem vrias maneiras de se percorrer uma rvore binria, s depende da tcnica criada pelo desenvolvedor implementar um algoritmo qualquer que percorra a rvore. Mas, a literatura trs algumas dessas tcnicas, que so as seguintes: a) Percorrer em Pr-Ordem O que fazemos no percurso em pr-ordem, seguir as regras: 1- Visitar a raiz; 2- Percorremos o lado esquerdo; 3- Percorrermos o lado direito; b) Percorrer Em Ordem

4 O que fazemos no percurso em ordem, seguir as regras: 1- Percorremos o lado esquerdo; 2- Visitar a raiz; 3- Percorrermos o lado direito; c) Percorrer em Ps-Ordem O que fazemos no percurso ps-ordem, seguir as regras: 1- Percorremos o lado esquerdo; 2- Percorrermos o lado direito; 3- Visitar a raiz; Observe que a diferena entre as 3 tcnicas somente a ordem que visitamos a raiz. Alm disso, sempre visitamos primeiro os descendentes esquerdos ao invs dos descendentes direitos. Ex.: A Pr-Ordem: ABDGCEHIF Ordem : DGBAHEICF Ps-Ordem: GDBHIEFCA C

D G F H G

E I

Pr-Ordem: ABCEIFJDGHKL Ordem : EICFJBGDKHLA Ps-Ordem: IEJFCGKLHDBA

C E G I F J G G

D H K G L

5 1.5. Operaes sobre rvores binrias Algumas funes podem ser implementadas para dar funcionalidade a manipulao de rvores binrias. Algumas dessas funes so: info(p) Retorna a informao contida em um n p letf(p) Retorna o n que est a esquerda de p right(p) Retorna o n que est a direita de p father(p) Retorna o n pai de p brother(p) Retorna o n irmo de p isleft(p) Retorna VERDADEIRO se o n p um n esquerda e FALSO se for direita. isright(p) Retorna VERDADEIRO se o n p um n direita e FALSO se for esquerda. A funo isleft, poderia ser implementada da seguinte forma (sendo tNo uma estrutura que representa o n): int isleft(tNo *p) { tNo *q; q = father(p); if(q == NULL) return 0; if(left(q) == p) return 1; return 0; } /* Fim da funo isleft() */ A funo brother, poderia ser implementada da seguinte forma (sendo tNo uma estrutura que representa o n): tNo * brother(tNo *p) { if (father(p) == NULL) return NULL; if (isleft(p)) return right(father(p)); return (left(father(p))); } /* Fim da funo brother() */

6 1.6. Representao de rvores Binrias 1.6.1 Representao com alocao dinmica de memria A estrutura abaixo ser utilizada para representao de ns de rvores binrias implementadas com alocao dinmica de memria: typedef struct no { tDado info; struct no *left, *right, *father; } tNo; O campo info pode ser de qualquer tipo. A estrutura tDado representa um bloco de informao composto de qualquer ou quaisquer tipos de informaes (registro de pessoas em uma locadora de vdeos, registro de alunos em um colgio ou universidade, etc.) A utilizao do campo father opcional. As primeiras tcnicas aqui mostradas para implementao de funes que percorrem a rvore, no utilizam esse campo, mas no final dessa seo, mostraremos um algoritmo que utiliza o campo father para percorrer a rvore. Antes de comear a apresentar os algoritmos implementados na linguagem C para representao e manipulao das rvores binrias, temos que considerar que sempre existe um n externo que aponta para a raiz da rvore binria. A primeira funo que apresentamos, a funo criaArvore, que cria um n na rvore, e o retorna a algum ponteiro externo: tNo * criaArvore(tDado dado) { tNo *p; p = malloc(sizeof(tNo)); pinfo = dado; pleft = NULL; pright = NULL; pfather = NULL; return p; } /*Fim da funo criaArvore() */ A prxima funo a funo setleft, que recebe um ponteiro p para um n qualquer e alguma informao, criando um novo n, descendente esquerdo do n p. void setleft(tNo *p, tDado dado) { if (p == NULL) printf(No existe um n pai!); else if (pleft != NULL) printf(O n esquerdo j exite!); else pleft = criaArvore(dado); } /* Fim da funo setleft() */

A prxima funo a funo setright, que recebe um ponteiro p para um n qualquer e alguma informao, criando um novo n descendente direito do n p. void setright(tNo *p, tDado dado) { if (p == NULL) printf(No existe um n pai!); else if (pright != NULL) printf(O n direito j exite!); else pright = criaArvore(dado); } /* Fim da funo setright() */ As funes mostradas a seguir, utilizam recursividade para implementar os percursos Pr-Ordem, Em Ordem e Ps-Ordem. A primeira apresentada ser o percurso em Pr-Ordem, como a seguir: void preOrdem(tNo *arvore) { if(arvore != NULL){ ImprimeDado(arvoredado); /* Visita a raiz */ preOrdem(arvoreesquerda); /* Caminha na sub-rvore esquerda */ preOrdem(arvoredireita); /* Caminha na sub-rvore direita */ } } /* Fim da funo preOrdem() */ A funo ImprimeDado, manipula as informaes do campo dado do n, da forma como o programador desejar. Essa funo pode ser implementada de qualquer forma (impresso de dados no monitor, gerao de relatrios, armazenamento em banco de dados, etc.). A segunda funo ser o percurso Em-Ordem, como a seguir: void emOrdem(tNo *arvore) { if(arvore != NULL){ emOrdem(arvoreesquerda); /* Caminha na sub-rvore esquerda */ ImprimeDado(arvoredado); /* Visita a raiz */ emOrdem(arvoredireita); /* Caminha na sub-rvore direita */ } } /* Fim da funo emOrdem() */

8 Por ltimo o percurso em Ps-Ordem: void posOrdem(tNo *arvore) { if(arvore != NULL){ posOrdem(arvoreesquerda); /* Caminha na sub-rvore esquerda */ posOrdem(arvoredireita); /* Caminha na sub-rvore direita */ ImprimeDado(arvoredado); /* Visita a raiz */ } } /* Fim da funo posOrdem() */ No percurso de cima da rvore (raiz at as folhas), no h a necessidade de utilizao do campo father, mas esse campo pode ser utilizado para percorrer a rvore. 1.6.2 Representao em Vetores (Representao Seqencial) Os ns numa rvore binria, para serem colocados em um vetor e com o intuito de facilitar o trabalho do desenvolver, devem ser organizados por sua cardinalidade, como o fazem as rvores binrias quase completas. Ex. 1 A 2 B 4 D 8 H G I 9 5 E 0 6 F 1 2 ...

3 C 7 G 8

A B C D E F G H I

Na representao seqencial, o n raiz alocado na posio de ndice 0 do vetor. Todos os outros ns so encontrados pelas equaes (sendo p o incide de um n qualquer): - 2p + 1 ndice no vetor do n esquerdo ao n de ndice p. - 2p + 2 ndice no vetor do n direito ao n de ndice p. Se um n esquerdo estiver no ndice p, o seu irmo direito estar no ndice p+1. Se um n direito est no ndice p, seu irmo esquerdo est no ndice de nmero p1.

9 Um n na representao seqencial mais simples que na representao por alocao dinmica, no necessitando assim dos ponteiros left, right ou father. A estrutura pode ser representada como segue: typedef struct no{ tDado info; int used; } tNo; O campo used recebe 1 (caso o n esteja sendo utilizado no vetor) e 0 (caso a sua posio esteja vazia, sem uso). Esse campo criado para ajudar na manipulao de rvores binrias representadas em vetores, que no sejam quase completas, isto , possuam posies vazias entre o n raiz e o n folha mais a direita. Ex. 1 A 2 B 6 D 12 F 13 G

3 C 7 E 0 1 2 ... 5 6 11 12 F G

A B C

D E

Para sabermos se algum n n esquerdo (isleft) ou direito (isright) de algum outro n, podemos descobrir pelas seguintes operaes (sendo p o ndice do n procurado): -p % 2 != 0 O n um n esquerdo (isleft) -p % 2 == 0 O n um n direito (isright) Em resumo, todos os ns esquerdos se encontram em ndices mpares e os ns direitos se encontram em ndices pares, sendo a raiz comeando no ndice 0. 1.7. rvores Binrias Costuradas (Encadeadas)

Uma rvore binria costurada, se caracteriza pelo fato de um n que possuir sua sub-rvore direita vazia, deve apontar para o seu n sucessor quando caminhamos em ordem. Assim, temos como exemplos as seguintes rvores:

10 Ex. A A

B B C C D E F E G G H I I F J G G K G H L D

As linhas tracejadas representam as costuras (ponteiros para os prximos ns na seqncia em ordem). Os ns na rvore binria costurada, possuem ainda um campo chamado costura que recebe os valores 1 (caso exista uma costura) e 0 (caso no exista costuras). Apesar da utilizao desse campo, o ltimo n a direita (ltimo em ordem), possui o campo costura setado em 1, apesar da costura no existir, tendo o seu ponteiro direito sendo apontado para NULL. Abaixo segue a estrutura do n nas rvores binrias costuradas: typedef struct no{ tDado info; struct no *left, *right; int costura; }tNo, *tArvore; A funo que cria a rvore binria costurada e retorna um ponteiro para o incio da rvore (raiz), mostrada abaixo: tArvore criaArvore(tDado dado) { tArvore p; p = malloc(sizeof(tNo)); pinfo = dado; pleft = NULL; pright = NULL; pcostura = 1; return p; } /* Fim da funo criaArvore() */ As prximas funes servem para criar os ns esquerda e direita a partir do n raiz, que se pressupe j foi criado pela funo criaArvore:

11 void setleft(tArvore p, tDado dado) { tArvore q; if(p == NULL) printf(No pode criar o n!); else if(pleft != NULL) printf(O n esquerdo j existe!); else { q = malloc(sizeof(tNo)); qinfo = dado; pleft = q; qleft = NULL; qright = p; qcostura = 1; } } /* Fim da funo setleft() */ /*********************/ void setright(tArvore p, tDado dado) { tArvore q, r; if(p == NULL) printf(No pode criar o n!); else if( !pcostura) printf(O n direito no pode ser criado); else { q = malloc(sizeof(tNo)); qinfo = dado; r = pright; pright = q; pcostura = 0; qleft = NULL; qright = r; qcostura = 1; } } /* Fim da funo setright() */ Depois da rvore binria costurada ser criada utilizando as funes definidas acima, temos abaixo a funo emOrdem que percorre a rvore costurada em ordem, utilizando as costuras criadas na funo setright.

12 void emOrdem(tArvore arvore) { tArvore p, q; p = arvore; do { q = NULL; while( p != NULL ) { q = p; p = pleft; } if ( q != NULL) { ImprimeDado(qinfo); p = qright; while (qcostura && p != NULL) { ImprimeDado(qinfo); q = p; p = pright; } /* Fim do while */ } /* Fim do if */ } while( q != NULL) /* Fim do do-while */ } /* Fim da funo emOrdem() */ 1.8. Percurso usando um campo father

Existe outro algoritmo para se percorrer uma rvore binria em ordem, desta vez utilizando um campo father, que at agora ainda no havia sido utilizado. O algoritmo apresentado abaixo menos eficiente do que o apresentado na seo anterior, mas tambm resolve o problema do percurso em ordem em rvores binrias.

13 void emOrdemFather() { tArvore p, q; p = arvore; q = NULL; do { while ( p != NULL) { q = p; p = pleft; } if (q != NULL) { ImprimeDado(qinfo); p = qright; } while (q != NULL && p == NULL) { do { p = q; q = pfather; } while (!isleft(p) && q != NULL); if (q != NULL) { ImprimeDado(qinfo); p = qright; } } /* Fim do while */ } while(q != NULL); /* Fim do do-while */ } /* Fim da funo emOrdemFather() */ 1.9. Representao de rvores por rvores Binrias

Toda rvore pode ser representada como uma rvore binria. A criao de estruturas para representar os ns de uma rvore qualquer de difcil implementao, j que no sabemos quantos ponteiros devem ser alocados para representar os filhos de um n dado qualquer. A vantagem de representarem rvores por rvores binrias que, nestas ltimas, apenas cerca da metade das ligaes so nulas. A representao em rvore binria deve seguir as regras abaixo: 1) Cada n da rvore binria ter como filho esquerda o filho mais a esquerda dele na rvore no binria; 2) Cada n ter como filho direita o seu irmo mais prximo na rvore no binria.

14 Ex.

A rvore binria transformada acima, pode ser ainda melhorada, girando os ns no sentido horrio, obtendo-se uma forma mais natural de representao de rvores binrias, como segue abaixo: A

1.10. Aplicaes de rvores Dentre as inmeras aplicaes de rvores, apenas uma ser vista aqui: aplicaes de rvores em tomadas de deciso. 1.10.1 rvores de Deciso Uma aplicao prtica de rvores no processo de tomada de deciso. Como ilustrao desse processo, considere o problema conhecido como problema das oito

15 moedas. Dadas as moedas a, b, c, d, e, f, g, e h, sabe-se que uma delas falsa e tem um peso diferente das demais. O objetivo determinar qual das moedas falsa, utilizando uma balana de braos iguais. Deseja-se fazer isto utilizando-se o menor nmero de comparaes e, ao mesmo tempo, determinar se a moeda falsa mais pesada ou mais leve que as demais. A rvore da figura a seguir representa um conjunto de decises pelas quais pode-se obter a resposta do problema. Por isso, tal rvore denominada de rvore de deciso. A utilizao de letras maisculas P ou L significa que a moeda falsa mais pesada ou mais leve que as demais.
a + b + c ? d + e + f

>

<

a + d ? b + e

g ? h

a + d ? b + e

>
a ? b

=
c ? a

<
b ? a
g ? a

>

<
h ? a a ? b

>

=
a ? c

<
b ? a

> =
a:P e:L

> =
c:P f:L

> =
b:P d:L

> =
g:P h:L

> =
h:P g:L

> =
b:L d:P

> =
c:L f:P

> =
a:L e:P

Observe que se a + b + c < d + e + f , ento sabe-se que a moeda falsa est presente entre estas seis moedas e no g nem h. Suponha que, na prxima medida, encontra-se que a + d < b + e, e, depois, trocando-se b por d, no se obtm nenhuma mudana na desigualdade. Isto significa duas coisas: (1) que nem c nem f falsa e (2) que nem b nem d falsa. Se a + d fosse igual a b + e, ento ou c ou f seria a moeda falsa. Sabendo neste ponto que a ou e a moeda falsa, comparase a com uma moeda boa (por exemplo, b). Se a = b, ento e mais pesada; caso contrrio, a deve ser mais leve que as demais moedas. Observando-se atentamente esta rvore da ltima figura, v-se que todas as possibilidades so cobertas, uma vez que h 8 moedas que podem ser mais leves ou mais pesadas, e h 16 ns terminais. Cada caminho requer exatamente 3 comparaes. Visualizar este problema como uma rvore de deciso muito til, mas esta rvore no fornece imediatamente um algoritmo. Para resolver o problema das oito moedas com um programa, deve-se escrever uma srie de testes que espelhem a estrutura da rvore. O programa em C a seguir reflete a soluo do problema das oito moedas de acordo com a rvore de deciso apresentada aqui. A funo Compare() utilizada para executar a ltima comparao da srie.

16 #include <stdio.h> typedef enum {A, B, C, D, E, F, G, H} tMoeda; void Compara(tMoeda x, tMoeda y, tMoeda z, unsigned *p) { if (p[x] > p[z]) printf("\nA moeda %c e' mais pesada.", 'A' + x); else printf("\nA moeda %c e' mais leve.", 'A' + y); } int main(void) { unsigned pesos[8]; tMoeda M; printf("Problema das Oito Moedas\n"); printf("======== === ==== ======\n\n"); for (M = A; M <= H; ++M) { printf("\nPeso da moeda %c: ", 'A' + M); scanf("%d", &pesos[M]); } if (pesos[A] + pesos[B] + pesos[C] == pesos[D] + pesos[E] + pesos[F]) if (pesos[G] > pesos[H]) Compara(G, H, A, pesos); else Compara(H, G, A, pesos); else if (pesos[A] + pesos[B] + pesos[C] > pesos[D] + pesos[E] + pesos[F]) if (pesos[A] + pesos[D] == pesos[B] + pesos[E]) Compara(C, F, A, pesos); else if (pesos[A] + pesos[D] > pesos[B] + pesos[E]) Compara(A, E, B, pesos); else Compara(B, D, A, pesos); else if (pesos[A] + pesos[B] + pesos[C] < pesos[D] + pesos[E] + pesos[F]) if (pesos[A] + pesos[D] == pesos[B] + pesos[E]) Compara(F, C, A, pesos); else if (pesos[A] + pesos[D] > pesos[B] + pesos[E]) Compara(D, B, A, pesos); else Compara(E, A, B, pesos); return 0; } /* Fim da funo compara() */

17 2. Busca de Dados Em programao, busca refere-se atividade de tentar encontrar alguma informao numa massa de dados a partir de dados incompletos relacionados com a informao procurada. Por exemplo, tentar encontrar os dados completos de um contribuinte de imposto de renda a partir de seu CPF uma atividade de busca. Definies fundamentais: Registro uma coleo de dados agrupados numa unidade. Exemplo: registro de aluno de uma universidade. Arquivo ou tabela uma coleo de registros. Exemplo: arquivo de todos os alunos de uma universidade. Chave: parte de um registro utilizado numa operao de busca. Exemplo: matrcula de aluno. Chave interna (embutida ou incorporada): faz parte do prprio registro. Exemplo: matrcula de aluno. Chave externa: no faz parte do registro a que se refere. Exemplo: num arquivo armazenado num arranjo unidimensional, o ndice associado a cada elemento (registro) do arranjo uma chave externa. Chave primria: nica para um conjunto de registros. Exemplo: matrcula de aluno. Chave secundria: no nica para um conjunto de registros. Exemplo: nacionalidade Algoritmo de busca: o Tenta encontrar um registro num arquivo cuja chave coincida com o valor recebido como entrada. o Tipicamente, um algoritmo de busca no retorna todo o registro encontrado, mas apenas um ponteiro para o mesmo. o Quando nenhum registro que casa com a chave de busca encontrado, o algoritmo de busca retorna um registro vazio ou, mais comumente, um ponteiro nulo. o Recuperao: busca bem sucedida (i.e., que encontra o registro procurado) Algoritmo de busca e insero: insere um novo registro com a chave de busca usada, se no consegue fazer uma recuperao. Dicionrio (ou tabela de busca): tabela (arquivo) na(o) qual feita uma busca. Busca interna: arquivo de busca encontra-se totalmente em memria principal Busca externa: parte do arquivo de busca encontra-se num meio de armazenamento externo (e.g., HD) Organizao de arquivo: o Vrias estruturas de dados podem ser usadas (e.g., arranjos, listas encadeadas, rvores) o Freqentemente, a organizao depende da tcnica de busca a ser usada. o

18 2.1 Busca Seqencial 2.2.1 Introduo Busca seqencial ou linear: Forma mais simples de busca Aplica-se a dados estruturados de modo linear (e.g., usando arranjos e listas encadeadas) Cada registro examinado a partir do incio da tabela e sua chave comparada chave de busca A busca continua at que seja encontrado um registro cuja chave casa com a chave de busca ou at atingir o final da tabela de busca a nica forma de encontrar algum registro numa lista onde os registros so organizados aleatoriamente Suponha a existncia dos seguintes arranjos: chaves[n]: um arranjo de n chaves, indexado de 0 a n 1 registros[n]: um arranjo de registros, do tipo tRegistro, associado ao arranjo chaves[n], tal que chaves[i] a chave de registros[i]. A funo a seguir retorna o menor ndice do registro que cuja chave casa com a chave de busca (i.e., o primeiro registro encontrado); se tal registro no for encontrado a funo retorna -1. Na funo, chave representa a chave procurada, chaves[] o arranjo de todas as chaves e nReg o nmero de registros: int EncontraIndice(int chave, int chaves[], int nReg) { int i; for (i = 0; i < nReg; ++i) if (chaves[i] == chave) return i; return 1; } /* Fim da funo EncontraIndice() */ A funo EncontraIndice() pode ser facilmente alterada para transform-la numa funo que faz busca e insero (ao invs de simplesmente busca). Mas, agora, necessrio acrescentar lista de argumentos o arranjo de registros e um argumento que informe o tamanho deste arranjo1:

No confunda os argumentos nReg e tamanho; nReg o nmero de registros atualmente armazenados no arranjo e este nmero pode ser alterado medida que se incluem ou removem registros; por outro lado, tamanho o tamanho com o qual o arranjo declarado e este valor fixo.
1

19

int BuscaEInsere(int chave,int registros[], int tamanho ) { int i;

chaves[],int

nReg,tRegistro

for (i = 0; i < *nReg; ++i) if (chaves[i] == chave) /* O registro foi encontrado */ return i; /* Neste ponto, sabe-se que o registro no foi encontrado */ if (nReg < tamanho) { /* H espao para insero */ chaves[nReg] = chave; /* Insere a chave */ RecebeRegistro(&registros[nReg]); /* Insere novo registro */ return (*nReg)++; /* Retorna nmero de registros incrementado */ }

return 1; /* Registro nem foi encontrado nem pode ser inserido */ } /* Fim da funo BuscaEInsere() */

A funo RecebeRegistro() chamada por BuscaEInsere() responsvel pela criao e atribuio do novo registro ao elemento do arranjo apropriado. Por exemplo, esta funo pode solicitar os dados ao usurio e, ento, atribuir os valores recebidos no elemento do arranjo. Utilizar uma lista encadeada para armazenar registros tem como grande vantagem a economia de espao. Suponha as seguintes declaraes de tipos, onde tRegistro um tipo previamente definido pelo programador, tNo o tipo de cada n da lista (tabela) e tTabela o tipo de um ponteiro para um n da lista: typedef struct no { tRegistro registro; struct no *proximo; } tNo, *tTabela; A funo a seguir semelhante funo anterior, mas utiliza uma lista encadeada para armazenar os registros2.
tRegistro *BuscaEInsere2(int chave, int chaves[], tTabela *tabela, int tamanho) { int i = 0; tNo *p, *q, *r = NULL; p = tabela; while (p != NULL){ q = p; if (chaves[i] == chave)
2

Evidentemente, se as chaves fossem internas (i.e., se a chave estivesse armazenada no prprio registro), no seria necessrio incluir o tamanho da tabela na lista de argumentos.

20
return &pregistro; i++; p = pproximo; } /* Fim do while */ if (i < tamanho) { /* D para inserir */ chaves[i] = chave; r = malloc(sizeof(tNo)); RecebeRegistro(&r->registro); r->proximo = NULL; if (tabela == NULL) /* Primeiro registro especial */ tabela = r; else q->proximo = r; } /* Fim do if */ if(r != NULL) return &rregistro; else return NULL; } /* Fim da funo BuscaEInsere2() */

Remoo de ns Arranjo: Implementada substituindo-se o registro a ser eliminado pelo ltimo registro do arranjo e decrementando-se o tamanho da tabela (este mtodo no se aplica se o arranjo estiver ordenado) Lista encadeada: remoo mais simples.

2.2.2 Busca Seqencial em Tabela Ordenada A eficincia de uma busca seqencial pode ser melhorada colocando-se os registros com mais probabilidade de serem acessados (i.e., aqueles com maior freqncia de acesso) no incio da tabela. Mtodos de busca: Movimentao para o incio Sempre que uma pesquisa obtiver xito, o registro recuperado movido para o incio da tabela. Eficiente apenas se a tabela for implementada em forma de lista encadeada Transposio Quando um registro recuperado, ele trocado pelo seu antecessor. Justificativa: Os mtodos baseiam-se na expectativa que um registro recuperado ser provavelmente recuperado novamente Assim, colocando tais registros na frente da lista, as recuperaes subseqentes sero mais rpidas

21 Raciocnio do mtodo de mover para a frente: como o registro ser recuperado novamente, ele deve ser colocado na melhor posio da tabela para que isto acontea (i.e., a frente da tabela) Raciocnio do mtodo de transposio: uma nica recuperao no implica que o registro ser recuperado com freqncia; se o registro for movido para frente apenas uma posio de cada vez, garante-se que ele estar na frente apenas se ele realmente tiver alta freqncia de busca.

Resultados: O mtodo de transposio mais eficiente para um grande nmero buscas e quando os registros so mais ou menos equiprovveis (i.e., se eles tm aproximadamente a mesma freqncia de acesso). O mtodo de mover para frente mais eficiente para um nmero de buscas entre pequeno e mdio e quando os registros no so to equiprovveis Na pior situao, o mtodo de mover para frente mais eficiente do que o mtodo de transposio; por isso, ele o preferido na maioria das situaes que requer busca seqencial. Vantagem do mtodo de transposio: pode ser aplicado eficientemente sobre arranjos e listas encadeadas A funo BuscaComTransposio(), apresentada a seguir, faz uma busca seqencial com transposio conforme descrito acima. Entretanto, diferentemente das funes anteriores, agora supe-se que a chave do tipo tChave e interna (i.e., faz parte de cada registro). A funo TestaChave() (no apresentada aqui) compara a chave de busca com a chave de cada registro e retorna 1 se estas casam e 0 em caso contrrio.
tRegistro *BuscaComTransposicao(tChave chave,tTabela *tabela) { tabela p, q = NULL, r = NULL; p = tabela; while (p != NULL && !TestaChave(chave, pregistro.chave)) { r = q; q = p; p = pproximo; } if (p == NUL) { return NULL; /* Registro no foi encontrado */ } /* Neste ponto, q aponta para o antecessor imediato */ /* de p e r aponta para o antecessor imediato de q */ if (q == NULL) /* Registro j o primeiro da lista */ return &p->registro; /* No necessria a transposio */ q->proximo = p->proximo; p->proximo = q;

22
if (r == NULL) /* Registro encontrado passa a ser o primeiro da lista */ tabela = p; else r->proximo = p; return &p->registro; } /* Fim da funo BuscaComTransposicao() */

Tabela Ordenada: Se a tabela estiver ordenada, podem-se usar tcnicas para aumentar a eficincia da busca Para determinar se uma chave existe numa tabela desordenada, so necessrias n comparaes Se a tabela estiver ordenada, so necessrias apenas n/2 comparaes em mdia (isso ocorre porque, se for achada uma chave maior do que a procurada, esta estar ausente) 2.2.3 Busca Seqencial Indexada Busca seqencial indexada: Usa uma tabela auxiliar (ndices) contendo pares (chave, ndice). As duas tabelas, tanto a de ndices, como a de busca, devem ter seus registros ordenados pela chave. Se a tabela de busca for n vezes maior do que a tabela de ndices, os elementos de ordem n, 2n, 3n, etc. da tabela de busca tero uma entrada na tabela de ndices. Em resumo, apenas alguns registros da tabela de busca possuem entradas na tabela de ndices. Ex.: indices[]
chave ndice

tabela[]
chave registro

321 592 876 321

592

876

23 Suponha a existncia dos seguintes arranjos: indices[nIndices]: um arranjo de nIndices pares do tipo:
typedef struct { unsigned unsigned } tIndiceChave; chave; indice;

tabela[nRegistros]:

um arranjo de nRegistros registros do tipo tRegistro

definido como:
typedef struct { unsigned chave; ... /* Outros campos do registro */ } tRegistro;

A funo a seguir retorna o ndice de um registro cuja chave casa com a chave de busca (i.e., o primeiro registro encontrado); se tal registro no for encontrado a funo retorna -1. Temos que chave a chave procurada, ndices[] o arranjo de ndices e tabela[] o arranjo de registros:
int EncontraIndice2(int chave,tIndiceChave nIndices,tRegistro tabela[],int nReg ) { int i, j, inferior, superior; indices[],int

for (i = 0; i < nIndices && indices[i].chave <= chave; ++i) ; inferior = (i == 0) ? 0 : indices[i-1].indice; superior = (i == nIndices) ? nReg - 1 : indices[i].indice - 1; for (j = inferior; j <= superior && tabela[j].chave != chave; ++j) ; return (j > superior) ? 1 : j; } /* Fim da funo EncontraIndice2 */

Observaes: Quando h vrios registros com a mesma chave, a funo de busca seqencial indexada no retorna necessariamente o ndice do primeiro desses registros na tabela. Vantagem da busca seqencial indexada: o tempo de busca consideravelmente reduzido, pois so feitas duas buscas sobre tabelas menores do que a tabela de busca original. O mtodo pode ser usado tambm quando a tabela representada em forma de lista encadeada; neste caso, inseres e remoes podem ser feitas com mais facilidade (rapidez). Causas de ineficincia: o a tabela de ndices grande demais para reduzir a busca o a tabela de ndices pequena demais, de modo que as chaves adjacentes so muito distantes

24 Soluo para ineficincias: usar uma tabela de ndices secundria, onde os ndices desta tabela apontam para uma entrada na tabela de ndices primria .

Remoo de registros: Mais eficiente se os registros a serem removidos forem marcados como tal Registros marcados so considerados ausentes Mesmo que a chave de um registro marcado esteja na tabela de ndices, no necessria nenhuma alterao nesta tabela. Insero de registros: Complicada quando no h espao entre dois registros j existentes, pois pode requerer deslocamento de grande parte dos registros da tabela. Mais simples se houver um item prximo que esteja marcado como removido Pode requerer alterao da tabela de ndices se um registro deslocado tiver entrada nesta tabela 2.3 Busca Binria Busca binria: Aplica-se apenas a tabelas de busca ordenadas em ordem crescente ou decrescente Semelhante a uma busca numa lista telefnica em ordem alfabtica: procura-se o nome desejado no meio da lista; se o nome procurado estiver na pgina central, a busca encerra-se; se o nome procurado estiver alm daqueles encontrados na pgina central, reduz-se a busca metade final do dicionrio; se o nome procurado estiver abaixo daqueles encontrados na pgina central, reduzse a busca metade inicial do dicionrio; ento, se for o caso, o procedimento repetido para a metade inicial ou final da lista. Algoritmo: 1. Compare a chave de busca com a chave do registro no meio da tabela 2. Se as chaves forem iguais, a busca encerrada com sucesso. 3. Se a chave de busca for maior do que a chave do registro, execute a busca na segunda metade da tabela. 4. Se a chave de busca for menor do que a chave do registro, execute a busca na primeira metade da tabela. 5. Se as chaves forem diferentes e a tabela contiver apenas um elemento, a busca encerrada sem sucesso. Observe que o algoritmo recursivo e que, em cada busca recursiva, o tamanho da tabela de busca reduzido metade. A denominao busca binria vem do fato de, antes de prosseguir, a tabela de busca ser dividida em duas partes iguais. A busca binria pode ser facilmente implementada como abaixo:
int EncontraIndiceB2(int chave, int chaves[], int nReg) { int limiteInf, limiteSup, metade;

25
limiteInf = 0; limiteSup = nReg - 1; while (limiteInf <= limiteSup) { metade = (limiteInf + limiteSup)/2; if (chaves[metade] == chave) return metade; if (chave < chaves[metade]) limiteSup = metade - 1; else limiteInf = metade + 1; } return 1; } /* Fim da funo EncontraIndiceB2 */

2.4 Busca por Interpolao Busca por interpolao: Aplica-se apenas a tabelas de busca ordenadas em ordem crescente ou decrescente Eficiente quando as chaves so uniformemente distribudas. Semelhante busca binria (isto , a busca realizada entre dois limites inf e sup) Diferentemente da busca binria, no divide a tabela em duas metades iguais. O meio (i.e., o local onde se espera encontrar a chave) calculado como:
(chave - chave(inf)) meio

= inf + (sup - inf)*

(chave(sup) - chave(inf))

Como na busca binria, se chave < chave(meio), redefine-se sup com meio 1 e, se chave > chave(meio), redefine-se inf com meio + 1; o processo repetido at que a chave seja encontrada ou inf > sup 0 30 1 40 2 50 3 60 4 70 5 80 6 90 7 100 8 110

Ex.

Na tabela acima, as chaves so uniformementes distribudas. Se a frmula for aplicada a esse caso em qualquer chave, teremos como resultado, exatamente o ndice da chave que procuramos. Desvantagens: Se as chaves no forem uniformemente distribudas, a busca por interpolao ser ineficiente. Os piores casos so quando a metade prxima de inf ou sup. Na prtica, as chaves freqentemente no so uniformemente distribudas (e.g., num catlogo telefnico, h mais nomes comeando com S do que com X).

26 A busca por interpolao envolve operaes aritmticas sobre chaves, multiplicaes e divises, enquanto que a busca binria envolve apenas operaes aritmticas bem mais simples; portanto, a busca por interpolao pode ser mais lenta mesmo quando envolve menos comparaes do que a busca binria.

2.5 rvores de Busca rvores de busca: As chaves so armazenadas em rvore binria A busca envolve um caminhamento na rvore Armazenamento das chaves: A primeira chave colocada na raiz Se a prxima chave for menor do que a chave na raiz e a sub-rvore esquerda estiver vazia, a nova chave colocada nesta posio; se a sub-rvore esquerda no estiver vazia, repete-se o procedimento a partir da raiz da sub-rvore esquerda. Se a prxima chave for maior do que ou igual a chave na raiz e a sub-rvore direita estiver vazia, a nova chave colocada nesta posio; se a sub-rvore direita no estiver vazia, repete-se o procedimento a partir da raiz da sub-rvore direita A funo CriaArvoreDeBusca() recebe um arranjo de chaves e armazena as chaves numa rvore binria de busca segundo o algoritmo delineado acima: Suponha a existncia da seguinte declarao de tipos3:
typedef } tDado; typedef struct no { struct no tDado struct no } tNo, *tArvore; struct { int chave; int indice;

*esquerda; dado; *direita;

O algoritmo abaixo cria uma rvore de busca. Tem como entradas o arranjo de chaves e o nmero total de chaves:
tArvore CriaArvoreDeBusca(int chaves[],int nChaves) { int i; tArvore arvore, p, q; tDado item; item.chave = chaves[0]; /* Recebe a primeira chave do vetor */ item.indice = 0; arvore = ConstroiArvore(item);
3

Suponha ainda a existncia das funes utilizadas na implementao de rvores binrias apresentadas na Seo 1.6.

27
for (i = 1; i < nChaves; ++i) { p = q = arvore; while (chaves[i] != p->dado.chave && q != NULL) { p = q; if (chaves[i] < p->dado.chave) q = p->esquerda; else q = p->direita; } item.chave = chaves[i]; item.indice = i; if (chaves[i] < p->dado.chave) FilhoEsquerda(p, item); else FilhoDireita(p, item); } return arvore; } /* Fim da funo CriaArvoreDeBusca() */

Exemplo: O arranjo de chaves:


14 15 4 9 7 18 3 5 16 20 17

Seria representado pela rvore:


14

15

18

16

20

17

28 Busca na rvore: A funo EncontraIndiceA() implementa a busca em rvore delineada no algoritmo acima.

No algoritmo abaixo, chave a chave procurada e p o ponteiro para o incio da rvore:

int EncontraIndiceAB(int chave, tArvore p) { while (p != NULL && chave != p->dado.chave) p = (chave < p->dado.chave) ? p->esquerda : p->direita; return (p != NULL) ? p->dado.indice : -1; } /* Fim da funo EncontraIndiceAB() */

30

Observaes: O caminhamento em in-ordem resulta na visitao em ordem crescente das chaves. A eficincia da busca pode ser melhorada usando um n sentinela: o Cada ponteiro nulo (esquerda e direita) da rvore passa a apontar para este n o Quando a busca executada, a chave colocada no sentinela, assegurando que ela ser encontrada o O ndice do n sentinela estabelecido como sendo -1 o O teste do while pode ser escrito apenas como: chave != arvore>dado.chave, economizando assim uma operao a cada passagem do lao A busca binria (v. Seo 2.3) usa um arranjo classificado como uma rvore binria implcita: o O elemento do meio do arranjo pode ser visto como a raiz desta rvore o Os elementos da metade inferior do arranjo formam a sub-rvore esquerda da rvore o Os elementos da metade superior do arranjo formam a sub-rvore direita da rvore Um arranjo de chaves classificado em ordem crescente pode ser produzido caminhando na rvore em in-ordem e inserindo as chaves seqencialmente no arranjo que 130 os ns so visitados 47 86 medida 95 115 138 159 166 184 206 212 219 224 237 258 296 307 314 Mas, existem vrias rvores binrias de busca que correspondem a um mesmo arranjo ordenado: 184 o Usar a estratgia de busca binria descrita acima produz uma rvore relativamente balanceada como mostra a figura a seguir:
115 237

47

138

212

296

30

86

130

159

206

219

258

307

95

166

224

314

29

o Usar o primeiro elemento do arranjo como raiz da rvore e os elementos subseqentes como filhos direitos produz uma rvore muito desbalanceada, como mostra a figura a seguir:

30

Vantagens do uso de rvores de busca com relao ao uso de arranjos: Operaes de busca, insero e remoo de chaves so mais eficientes. Insero e remoo de chaves em arranjos envolvem movimento de elementos do mesmo. Insero e remoo de chaves em rvores de busca envolvem apenas a alterao de ponteiros. 2.5.1 Insero de Ns A funo BuscaEInsere3(), apresentada a seguir, faz uma busca numa rvore de busca e insere um novo n na rvore se a busca no obtiver xito.
int BuscaEInsereAB(int chave, tArvore arvore, int nReg,tRegistro registros[],int tamanho) { tArvore p, q, r; tDado item; p = arvore; q = NULL; while (p != NULL) { if (chave == p->dado.chave) /* A chave foi encontrada */ return p->dado.indice; q = p; if (chave < p->dado.chave) p = p->esquerda; else p = p->direita;

/* Neste ponto, sabe-se que o registro no foi encontrado */ if (nReg < tamanho) { /* H espao para insero */ item.chave = chave; item.indice = nReg; r = ConstroiArvore(item); /* Constri novo n */ if (q == NUL) arvore = r; else if (chave < p->dado.chave) q->esquerda = r; else q->direita = r; RecebeRegistro(&registros[nReg]); /* Insere novo registro */ return (nReg)++; /* Retorna nmero de registros incrementado */ } return 1; /* Registro nem foi encontrado nem pode ser inserido */ } /* Fim da funo BuscaEInsereAB() */

31 2.5.2 Remoo de Ns Para remover um n de uma rvore binria de busca devem-se considerar trs casos:

Caso 1: o n a ser removido no possui filhos. Neste caso, ele pode ser eliminado sem outros ajustes na rvore, conforme mostra a figura a seguir:
8 8

11

11

14

14

10

12

15

10

12

13

13

Eliminao do n com chave = 15 15 Caso 2: o n a ser removido possui apenas um filho. Neste caso, o nico filho movido para cima 8 para ocupar o lugar do n removido, conforme mostra 8 a figura a seguir:

11

11

14

14

10

12

15

10

12

15

13

13

Eliminao do n com chave = 5

32

Caso 3: o n a ser removido possui dois filhos. Neste caso, o sucessor in-ordem deve ocupar o lugar do n removido. Este sucessor no pode ter filho esquerda. Assim, o filho direito deste sucessor pode ser movido para cima para ocupar o lugar do prprio sucessor. Este caso ilustrado na figura a seguir:
8 8

11

12

14

14

10

12

15

10

13

15

13

Eliminao do n com chave = 11 A funo RemoveAB(), apresentada a seguir, remove um n de uma rvore binria de busca levando estes trs caso em considerao.
int RemoveAB(int chave, tArvore arvore, tRegistro registros[],int nReg) { tArvore p, q, r, s, pai; p = arvore; q = NULL; /* p apontar para o n contendo a chave procurada */ /* q apontar para o pai de p */

while (p!=NULL && chave != p->dado.chave) { q = p; p = (chave < p->dado.chave) ? p->esquerda : p->direita; }

33
if (p == NULL) /* A chave no foi encontrada */ return 0; /* No ocorreu remoo */ /* Faz r apontar para o n que substituir p */ if (p->esquerda == NULL) /* O n a ser eliminado tem no mximo um filho */ r = p->direita; else if (p->direita == NULL) /* O n a ser eliminado tem no mximo um filho */ r = p->esquerda; else { /* p tem dois filhos. Neste caso, faz-se r apontar para o */ /* sucessor in-ordem de p e pai apontar para o pai de r */ pai = p; r = p->direita; s = r->esquerda; /* s sempre o filho esquerda de r */ while (s!=NULL) { pai = r; r = s; s = r->esquerda; } /* Neste ponto, sabe-se que r o sucessor in-ordem de p */ if (pai != p) { /* p no o pai de r e r igual a pai-> esquerda pai->esquerda = r->direita; /* Remove r de sua posio atual e o substitui pelo */ /* filho direito de r; r ocupar o lugar de p */ r->direita = p->direita; /* Faz o n apontado por r ocupar o lugar do n apontado por p */ } if (q==NULL) /* O n removido era a raiz */ arvore = r; else (p == q->esquerda) ? (q->esquerda = r) : (q->direita = r); /* Remove o registro da tabela. */ /* Estamos supondo que a remoo do registro foi OK. */ /* Mas, rigorosamente, isto no deveria ser feito. */ RemoveDaTabela(registros, nReg, p->dado.indice); free(p); /* Libera o n removido */ return 1; /* A remoo foi bem sucedida */ } /* Fim da funo RemoveAB() */ r->esquerda = p->esquerda;

*/

A funo RemoveAB(), apresentada acima, utiliza a funo RemoveDaTabela() que remove da tabela o registro de ndice especificado. A implementao desta funo relativamente fcil e deixada como exerccio.