Академический Документы
Профессиональный Документы
Культура Документы
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.
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
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