Вы находитесь на странице: 1из 10

Programao II 2009/2010 Notas das Tericas Joo Pedro Neto

(com extenses, 2010/2011, Beatriz Carmo)

Pesquisa A pesquisa uma tarefa muito genrica. Um algoritmo que resolve um problema pode procurar a soluo percorrendo um nmero de candidatos a soluo e devolver a soluo ptima. Isto significa que o algoritmo deve ser capaz de gerar solues e ter um critrio que verifique se a soluo actual a indicada. Um subconjunto desta noo a procura de um elemento especfico sobre uma estrutura de dados (eg, o mximo, o elemento cuja chave K). Dependendo da estrutura de dados e do que sabemos sobre a sua organizao, podemos melhorar os nossos algoritmos de pesquisa para minimizar a complexidade dos mesmos. Pesquisa sequencial Por exemplo, para pesquisar um elemento numa lista simplesmente ligada, inevitvel percorr-la sequencialmente (pesquisa sequencial), uma vez que apenas temos acesso directo ao primeiro elemento da lista e, em cada elemento, apenas temos acesso ao elemento seguinte. Se a lista no estiver ordenada, avanamos na lista enquanto no chegarmos ao fim da lista e no encontrarmos o elemento que procuramos. Se a lista estiver ordenada, podemos juntar mais uma condio de paragem: encontrar um elemento maior do que aquele que procuramos. Ou seja, avanamos na lista enquanto no chegarmos ao fim da lista e no encontrarmos um elemento maior ou igual ao que procuramos. Quer a lista esteja ordenada quer no esteja, no pior caso temos de percorrer toda a lista, o que leva a que a pesquisa sequencial tenha uma complexidade linear (O(n)). Pesquisa binria Num vector temos acesso directo a qualquer posio, atravs do seu ndice. Se o vector estiver ordenado, podemos melhorar o desempenho da pesquisa usando uma pesquisa binria. Na pesquisa binria: Consulta-se o elemento do meio e verifica-se se maior ou menor que o elemento que se procura. Se for menor, repete-se a pesquisa para a 1 metade do vector, caso contrrio, faz-se para a 2 metade. Este algoritmo pesquisa o valor em O(log n).

Exemplo1 Procurar 20 0 1 2 3 4 5 6 7 8 9 3 7 20 25 30 34 42 44 50 28 ndice do meio = ndice do incio + (ndice do fim ndice do incio) / 2 ndice do meio = 4 28>20, ento procurar na metade esquerda 0 1 2 3 3 20 25 7 ndice do meio = 1 7<20, ento procurar na metade direita 2 3 25 20 ndice do meio = 2 20 =20, encontrado! Exemplo2 Procurar 35 0 1 2 3 4 5 6 7 8 9 3 7 20 25 30 34 42 44 50 28 ndice do meio = ndice do incio + (ndice do fim ndice do incio) / 2 ndice do meio = 4 28<35, ento procurar na metade direita 5 6 7 8 9 30 34 44 50 42 ndice do meio = 7 42>35, ento procurar na metade esquerda 5 6 34 30 ndice do meio = 5 30<35, ento procurar na metade direita 6 34 ndice do meio = 6 34 35, no encontrado!

/* Se encontrar o valor, devolve o ndice da sua posio, * caso contrrio devolve -1 */ int pesq_bin (int vec [], int inicio, int fim, int valor) { int minimo = inicio, maximo = fim, meio, pos= -1; while (minimo <= maximo && pos==-1) { meio = minimo + (maximo-minimo)/2; /* divisao inteira */ if (vec [meio] == valor) pos=meio; else if (vec [meio] < valor) minimo = meio + 1; /* procura na metade direita */ else maximo = meio - 1; /* procura na metade esquerda */ } return pos; }

Curiosamente, se o acesso estrutura de dados for mais complexo (eg, acesso ao ficheiro) a pesquisa linear pode ser mais eficiente para dimenses menores. Podem misturar-se duas abordagens, usando pesquisa binria quando os saltos so maiores que, digamos, 32 elementos, e passando a pesquisa linear quando descemos esse valor. Usa-se mnimo+(mximo-minimo)/2 em vez de (mnimo+mximo)/2 para no gerar valores inteiros que possam exceder a capacidade da representao de inteiros. A pesquisa binria to til que a biblioteca stdlib tem este servio. int ord[] = {1, 5, 8, 9, 11, 14, 20}; int key; int *elem; key=19; elem = bsearch(&key, ord, 7, sizeof(int), comp); if (elem==NULL) printf("nao encontrou"); else printf("encontrou o %d", *elem); int comp(const void *m1, const void *m2) { const int *a = (const int*)m1; const int *b = (const int*)m2; return *a - *b; }

Pesquisa ternria Na pesquisa ternria o vector dividido em trs partes, em vez de duas, como acontece na pesquisa binria. Na pesquisa ternria preciso determinar dois elementos pivot em relao aos quais se faz a comparao com o valor que procuramos. Estes elementos esto nas posies 1/3 e 2/3 do vector. Os seus ndices podem determinar-se com as seguintes expresses: ind_ pivot1 = ndice do incio + (ndice do fim ndice do incio) / 3 ind_pivot2 = 2 * ind_ pivot1 Pesquisa interpolada Uma forma mais inteligente de pesquisa atravs de uma pesquisa interpolada. Este algoritmo imita a forma como os humanos pesquisam um dicionrio, dando saltos maiores ou menores consoante a distncia estimada a que nos encontramos da chave. Neste caso a determinao do ndice do pivot depende do valor que se procura e dos valores mximo e mnimo do intervalo de procura: fraccao= (valor v[ind_inicio]) / (v[ind_fim] - v[ind_inicio]) ind_pivot = ind_inicio + fraccao * (ind_fim - ind_inicio) em que fraccao [0,1] A pesquisa interpolada, se as chaves esto uniformemente distribudas, tem complexidade O(log (log n)). Mas na prtica, para a maioria das situaes, o seu desempenho no melhor que a pesquisa binria (tem de realizar muito mais operaes de aritmtica). E a complexidade no pior caso, para um vector qualquer, O(n). Deve ser usado somente em casos com milhes de elementos, uniformemente distribudos e onde cada comparao seja muito dispendiosa. Pesquisa por disperso A pesquisa por disperso o tipo de pesquisa efectuado em tabelas de disperso.

Tabelas de disperso (Hash tables)


Num vector podemos aceder imediatamente a um elemento, se soubermos o seu ndice. Esta ideia pode ser usada para qualquer tipo de informao desde que exista uma chave que identifique cada registo como nico. Porm, para chaves com muitas combinaes, impossvel reservar um vector onde se possam colocar todos os registos no ndice prprio. Uma tabela de disperso (hash table) uma implementao em que o valor da chave, associado ao elemento, usado para calcular a posio onde esse elemento deve ser inserido e procurado. Pressupomos a existncia de uma funo h( ) que relaciona a chave (seja um tipo K) e o conjunto de endereos da estrutura de dados (designado por A). Esta funo h designada por funo de disperso (do ingls, hash function): h:KA O problema principal quando se pretende encontrar esta funo de disperso que o espao disponvel para armazenar as chaves muito menor que o nmero de combinaes possveis de valores da chave, i.e., |K| >> |A|. Ou seja, na maioria dos casos, a funo de disperso no injectiva. Existem diversas chaves que quando aplicada h vo resultar no mesmo endereo. Este tipo de problema designado por coliso. Se a funo de disperso fosse bijectiva representaria uma disperso perfeita sem a existncia de colises. Nos casos em que |K| >> |A| essa funo perfeita no possvel. E de facto, uma anlise combinatria revela que as probabilidades de coliso so bastante altas mesmo para um reduzido conjunto de elementos inseridos. Como exemplo a probabilidade de, num conjunto de 23 pessoas, encontrarmos duas com a mesma data de aniversrio maior que 50%! Encontramos assim, dois problemas a resolver: Que funo h usar? Como fazer para resolver potenciais colises?

Para a primeira questo preciso conhecer os detalhes especficos do problema (qual o tipo da chave? Qual a memria disponvel, i.e., qual a cardinalidade de A?). Consideramos que o vector onde armazenada a informao possui uma dimenso N (desde o ndice 0 at ao ndice N1). Admitimos igualmente a existncia de uma funo (designada por ord) que transforma a chave (de um tipo arbitrrio) num valor inteiro. A forma de definir h: h(k) = ord(k) mod N A operao mod o resto da diviso inteira (aritmtica mdulo N). Esta funo foi utilizada na implementao esttica das filas de espera para criar um vector circular. Deve-se escolher um nmero primo para o valor de N devido ao seu pequeno nmero de divisores (ver a sequncia de pesquisa descrita adiante). Existem tcnicas de processamento inicial da chave que se reflectem na funo ord( ): Se partes da chave possuem pouca diversidade, convm remov-las antes de aplicar a funo de disperso. Por exemplo, se a chave o nmero do BI e o universo dos elementos a inserir so pessoas com sensivelmente a mesma idade, a variao dos dgitos na ordem dos milhes muito pequena. Se a chave uma palavra, preciso convert-la num inteiro. A funo seguinte um exemplo simples de converso.

int ord (char str[]) { unsigned int i, dim=strlen(str), total=0; for (i=0;i<dim; i++) total=total+str[i]; return total; }

Por vezes convm dividir a chave em partes e agrup-las de alguma forma. Esta forma pode melhorar a variabilidade da chave inicial. Por exemplo, sendo uma chave uma palavra (i.e., uma sequncia de caracteres) atribui-se a cada caracter um valor e adicionam-se para criar um nmero. Por exemplo, ord(zebra) = ord(z)*3 + ord(e)*32 + ord(b)*33 + ord(r)*34 + ord(a)*35, onde o valor de uma letra igual posio no alfabeto (i.e., ord(a) = 1, ord(b) = 2, etc.).

A segunda questo como se resolvem colises? depende essencialmente da estrutura de dados utilizada: o endereamento fechado, que usa um vector de listas, e o endereamento aberto, que usa simplesmente um vector. Resoluo de colises com endereamento fechado No endereamento fechado (do ingls, closed addressing ou direct/separate chaining) a tabela de disperso um vector de listas, armazenando-se todos os elementos cuja chave devolve o mesmo valor da funo de disperso na lista da posio correspondente do vector. Neste exemplo, numa tabela com quatro posies, foram inseridos elementos com chaves a a g (omite-se a informao associada chave por motivos de clareza j que no relevante para a explicao) usando uma funo de disperso que nas chaves em questo devolveu h(a)=0, h(b)=2, h(c)=3, h(d)=2, h(e)=0, h(f)=2 e h(g)=2.
0 e a / / g f d b / c /

No endereamento fechado, a coliso resolvida inserindo o novo elemento cabea do objecto lista. Todos os elementos cuja chave aplicada sobre a funo de disperso devolve o mesmo valor encontram-se na mesma lista. A soluo apresentada por este mtodo para resolver as colises bastante directa mas possui uma potencial desvantagem: apesar do algoritmo de insero (inserir na cabea da lista) ser O(1), se a funo de disperso no realiza o trabalho de forma efectiva (i.e., os elementos vo-se acumulando em poucas listas), a complexidade da busca pode degenerar numa busca linear, dado ser necessrio procurar dentro de uma lista progressivamente maior pelo elemento pretendido. No exemplo acima, poder-se-ia

suspeitar da existncia de uma assimetria observando a posio de valor 1 (com zero elementos) e a de valor 2 (com quatro elementos). Algoritmo para inserir um elemento na tabela - Calcular o valor da funo de disperso para o elemento (seja pos esse valor) - Inserir o elemento no incio da lista que est na posio pos do vector. Algoritmo para apagar um elemento na tabela - Calcular o valor da funo de disperso para o elemento (seja pos esse valor) - Apagar o elemento, caso exista, na lista da posio pos do vector. Algoritmo para procurar um elemento na tabela - Calcular o valor da funo de disperso para o elemento (seja pos esse valor) - Procurar o elemento na lista da posio pos do vector. Resoluo de colises com endereamento aberto O endereamento aberto (do ingls, open addressing) representa a tabela com uma estrutura de dados vectorial, em que cada posio armazena, no uma lista, mas apenas um e um nico registo. Quando uma posio est ocupada preciso procurar outra posio. Qual? necessrio fornecer uma segunda funo que permita encontrar uma ou mais localizaes alternativas para a chave que provocou a coliso. Esta nova funo designa-se por funo de pesquisa e permite resolver as eventuais colises que surjam (do ingls, collision handling). A funo de pesquisa recebe dois argumentos: a chave do elemento a procurar/inserir e um valor inteiro n que representa a n-sima tentativa. h p : K 0 A conveniente que esta funo garanta as condies seguintes: hp (k,0) = h(k), i.e., a primeira tentativa procura na posio indicada pela funo de disperso. Dado o vector (que guarda a informao) de dimenso N, a sequncia de valores hp (k,0), hp (k,1), hp (k,2), ..., hp (k,N-1) deve passar por todos os valores possveis do vector. Esta sequncia designa-se por sequncia de pesquisa (do ingls, probing sequence). A segunda condio necessria porque desde que a tabela tenha pelo menos uma posio livre, o algoritmo de pesquisa deve ser capaz de encontr-la. Idealmente, a funo de pesquisa deve satisfazer: hp (k,0) = hp (k,N). Assim, se no existirem posies livres, a funo ir chegar ao local de partida: a memria est cheia. O mtodo mais simples designado por pesquisa linear (do ingls, linear probing): hp (k,i) = (h(k) + i) mod N Exemplo Inserir pela ordem indicada os elementos com os seguintes valores da funo de disperso: h(a)=2, h(b)=2, h(c)=4, h(d)=2, h(f)=7, h(g)=9, h(h)=9

Aps inserir o primeiro elemento (a) temos 0 1 2 a 2 a 3 4 5 6 7 8 9

Como a posio 2 est ocupada, b inserido na posio 3 0 1 3 b 4 5 6 7 8 9

Insere c na posio 4. Ao inserir d, a posio 2 est ocupada e a primeira posio livre a 5. 0 1 2 a 3 b 4 c 5 d 6 7 8 9

Aps inserir f , g e h temos 0 h 1 2 a 3 b 4 c 5 d 6 7 f 8 9 g

Este mtodo, apesar da simplicidade, tem a desvantagem dos elementos se agruparem em aglomerados (do ingls, clusters) volta de uma ou mais chaves relativamente prximas. Este detalhe diminui o desempenho da busca, degenerando em pesquisas lineares quando ocorrem sobre aglomerados. Uma forma mais geral desta pesquisa: hp (k,i) = (h(k) + a.i) mod N O valor constante a deve ser primo em relao a N (i.e., o mximo divisor comum de a e N 1). Se o m.d.c fosse x>1, apenas 1/x da tabela seria visitada pela sequncia de pesquisa. Uma das formas mais eficazes de distribuir os elementos com a mesma chave atravs de uma pesquisa com disperso dupla. Neste mtodo necessrio uma segunda funo de disperso para ser usada na sequncia de pesquisa (designada por h1) : hp (k,i) = (h(k) + i.h1(k)) mod N Mesmo que as chaves de dois elementos produzam o mesmo valor quando aplicada a funo h, os valores j sero diferentes para a funo h1 diminuindo assim a criao de aglomerados. Este mtodo considerado como a melhor forma para criar uma sequncia de pesquisa. At agora foram referidas tcnicas para insero e pesquisa sobre uma tabela de disperso. Como se efectua a remoo? No caso do endereamento fechado, a soluo trivial: remover o elemento da lista. Para o endereamento aberto a soluo no to directa. No se pode remover simplesmente o elemento da tabela. Retomando o exemplo j apresentado, em que se usou uma sequncia com pesquisa linear com: h(a)=2, h(b)=2, h(c)=4, h(d)=2 0 h 1 2 a 3 b 4 c 5 d 6 7 f 8 9 g

Do aglomerado com quatro elementos (a, b, c, d), o elemento c est na posio que a funo de disperso lhe atribuiu inicialmente, enquanto a, b e d possuem chaves com o mesmo ndice, razo pela qual b e d esto em posies resultantes de uma e trs colises. Se removermos o elemento b sem mais nenhuma alterao, essa posio considerada vazia. O que acontece se procurarmos d? 0 h 1 2 a 3 4 c 5 d 6 7 f 8 9 g

A funo de disperso, dada a chave de d, devolve o ndice onde se situa a. Como no encontra d, a sequncia de pesquisa passa para a prxima posio onde encontra uma posio vazia e, por isso, pra. A resposta seria que d no existe na tabela. O que aconteceu? A posio de onde b foi removido no se encontra verdadeiramente vazia, pois ainda faz parte de um aglomerado (no o dividiu em dois). A posio est vaga, no vazia. necessrio substituir o elemento removido por um smbolo alternativo que represente esse facto (designemos por ). 0 h 1 2 a 3 4 c 5 d 6 7 f 8 9 g

Assim, quando o algoritmo procura d, vai passar pela posio vaga e continua at encontrar o elemento ou uma verdadeira posio vazia. Se a resoluo de colises for feita por pesquisa linear, temos os seguintes algoritmos. Algoritmo para inserir um elemento na tabela - Calcular o valor da funo de disperso para o elemento (seja pos esse valor) - Procurar a primeira posio vazia do vector a partir da posio pos. (Se chegar ltima posio, continua na posio zero). - Inserir o elemento na posio vazia. Algoritmo para apagar um elemento da tabela - Calcular o valor da funo de disperso para o elemento (seja pos esse valor) - Procurar o elemento no vector a partir da posio pos at o encontrar ou encontrar uma posio vazia. - Se encontrou o elemento, marcar a posio como apagada. Algoritmo para procurar um elemento na tabela - Calcular o valor da funo de disperso para o elemento (seja pos esse valor) - Procurar o elemento no vector a partir da posio pos at o encontrar ou encontrar uma posio vazia. Factor de carga ou de saturao O factor de carga ou de saturao (do ingls, load factor), , igual ao nmero de posies ocupadas a dividir pelo nmero total de posies. Atravs de anlise

estatstica do caso mdio, possvel determinar o nmero esperado de colises em funo do factor de carga. Por exemplo, no caso da resoluo de colises por endereamento aberto e pesquisa linear, o nmero mdio de colises : C= 1/2 1

Se =0.5 (i.e., metade da tabela est ocupada), C=1.5. Se =0.95 ento C=10.5, isto , mesmo com 95% da tabela ocupada, o mtodo de pesquisa mais fraco procura, em mdia, apenas 10 ou 11 posies. No caso da resoluo de colises por endereamento fechado, o factor de carga corresponde ao nmero mdio de elementos por lista. O nmero mdio de colises : C = 1 + /2 Qualquer que seja o mtodo de resoluo de colises usado, , por vezes, conveniente redimensionar a tabela quando o factor de carga atinge um determinado valor pr-estabelecido. Esta operao reduz o factor de carga e, consequentemente, o nmero mdio de colises. Por outro lado, esta operao pode ser usada para garantir a existncia de espao para insero de novos elementos. Redimensionar a tabela consiste em criar uma tabela maior e voltar a inserir os elementos um a um na nova tabela. Pode usar-se como nova dimenso da tabela o nmero primo seguinte ao dobro da dimenso corrente da tabela. Uma vez que a dimenso da tabela, N, muda, necessrio recalcular a funo de disperso para todos os elementos.

10

Вам также может понравиться