Busca Banco de dados existem para que, de tempos em tempos, um usuário possa localizar o dado de um registro, simplesmente digitando sua chave. Uma tabela ou um arquivo é um grupo de elementos, cada um dos quais chamado registro. Existe uma chave associada a cada registro, usada para diferenciar os registros entre si. O objetivo da pesquisa é encontrar uma ou mais ocorrências de registros com chaves iguais à chave de pesquisa. Um algoritmo de busca é aquele que aceita um argumento a e tenta encontrar o registro cuja chave seja a. O algoritmo pode retornar o registro inteiro ou um ponteiro para esse registro. Uma tabela ou arquivo pode ser um vetor de registros, uma lista ligada, uma árvore ou até um gráfico. Existe uma variedade de métodos de pesquisa. A escola do método de pesquisa mais adequado a uma determinada aplicação depende principalmente: Da quantidade dos dados envolvidos, Do arquivo estar sujeito a inserções e retiradas frequentes, ou do conteúdo do arquivo ser praticamente estável. É importante considerar os algoritmos de pesquisa como tipos abstratos de dados, com um conjunto de operações associado a uma estrutura de dados, de tal forma que haja uma independência de implementação para as operaçõesalgumas das operações mais comuns incluem: 1. Inicializar a estrutura de dados. 2. Pesquisar um ou mais registros com determinada chave. 3. Inserir um novo registro. 4. Retirar um registro específico. 5. Ordenar um arquivo para obter todos os registros em ordem de acordo com a chave. 6. Ajuntar dois arquivos para formar um arquivo maior. Pesquisa sequencial Encontrar informações em uma matriz desordenada requer uma pesquisa sequencial, começando no primeiro elemento e parando quando o elemento procurado ou o final da matriz é encontrado. Esse método deve ser usado em dados desordenados, mas também pode ser aplicado a dados ordenados. É fácil de ser codificada. Funciona da seguinte forma: a partir do primeiro registro, pesquise sequencialmente até encontrar a chave procurada; então pare. Exemplo 1: a função a seguir faz uma pesquisa em uma matriz de caracteres de comprimento conhecido até que seja encontrado, a partir de uma chave específica, o elemento procurado: busca_seq(char *item, int cont, char key){ register int t; for (t = 0; t < cont; ++t) if (key==item[t]) return t; return -1;
Esse algoritmo de busca pode ser muito ineficiente quando o número de elementos no vetor for muito grande. O desempenho computacional desse algoritmo varia linearmente com relação ao tamanho do problema. Se os elementos estiverem armazenados em uma ordem aleatória no vetor, não é possível melhorar o algoritmo de busca, pois será preciso verificar todos os elementos. Porém, se os elementos estão armazenados em ordem crescente, podemos concluir que um elemento não está no vetor se acharmos um elemento maior. Exemplo 2: o código abaixo ilustra a implementação da busca linear assumindo que os elementos do vetor estão ordenados em ordem crescente. buscaord(char *item, int cont, char key){ register int t; for (t = 0; t < cont; ++t){ return -1; if (key==item[t]) return t; //elemento encontrado else if(key < item[t]) return -1; //interrompe busca //elemento nao encontrado Pesquisa binária O método mais eficiente de pesquisar uma tabela seqüencial é a busca binária. Esse método é utilizado se os registros forem mantidos em ordem. Esse método verifica o elemento central. Se esse elemento é maior que a chave, ele testa o elemento central da primeira metade; caso contrário, ele testa o elemento central da segunda metade. Esse procedimento é repetido até que o elemento seja encontrado ou que não haja mais elementos a testar. Exemplo de pesquisa binária para a chave G. Exemplo 3: o código mostra uma pesquisa binária para matrizes de caracteres. busca_bin(char *item, int cont, char key){ int menor, maior, meio; menor = 0; maior = cont-1; while (menor<=maior){ meio = (menor+maior)/2; if (key < item[meio]) maior = meio -1; else if (key > item[meio]) menor = meio+1; else return meio; return -1; O desempenho desse algoritmo é muito superior ao de busca linear.
Árvore binária de busca A árvore de pesquisa é uma estrutura de dados muito eficiente para armazenar informação. Ela é particularmente adequada quando existe necessidade de considerar todos ou alguma combinação de requisitos, tais como: Acesso direto e sequencial eficientes. Facilidade de inserção e retirada de registros. Boa taxa de utilização de memória. Utilização de memória primária e secundária. Considere o caso em que a informação associada a um nó é um número inteiro, e não há a possibilidade de repetição de valores associados aos nós da árvore. Exemplo de árvore binária de busca de valores inteiros. O tipo da árvore binária pode ser dado por: struct arv{ int info; struct arv *esq; struct arv *dir; ; typedef struct arv Arv; A árvore é representada pelo ponteiro para o nó raiz. A árvore é inicializada atribuindo-se NULL a variável que represente a árvore. Arv* inicializa (void){ return NULL; Uma vez construída uma árvore de busca, podemos imprimir os valores da árvore em ordem crescente, percorrendo os nós em ordem simétrica. void imprime (Arv* a){ if (a!= NULL){ imprime (a->esq); printf("%d\n",a->info); imprime(a->dir); Essas funções são análogas às vistas para árvores binárias comuns. Operação de busca Para buscar um elemento na árvore é explorada a propriedade de ordenação da árvore, tendo um desempenho computacional proporcional a sua altura. Arv* busca (Arv* r, int v){ if (r==null) return NULL; else if (r->info > v)
return busca (r->esq,v); else if (r->info < v) return busca(r->dir,v); else return r; Essa função recebe como parâmetros o ponteiro para a árvore e o valor inteiro a ser buscado. Primeiro verifica se a árvore está vazia. Caso não esteja vazia, se o valor procurado for menor que o valor do nó, procura-se no lado esquerdo. Caso não seja vazia, e o valor procura seja maior que o valor do nó, procura-se no lado direito. Caso contrário, encontrou o valor. Operação de inserção Adiciona um elemento na árvore na posição correta para que a propriedade fundamental seja mantida. Para inserir um valor v em uma árvore usamos sua estrutura recursiva, e a ordenação especificada na propriedade fundamental. Se a (sub) árvore é vazia, deve ser substituída por uma árvore cujo único nó (o nó raiz) contém o valor v. Se a árvore não é vazia, comparamos v com o valor na raiz da árvore, e inserimos v na sub-árvore esquerda (sae) ou na sub-árvore direita (sad), conforme o resultado da comparação. Arv* insere (Arv* a, int v){ if (a == NULL){ a = (Arv*)malloc(sizeof(Arv)); a->info = v; a->esq = a->dir = NULL; else if (v < a->info) a->esq = insere(a->esq,v); else a->dir = insere(a->dir,v); return a; Operação de remoção Permite retirar um determinado elemento da árvore. Existem três situações possíveis: 1ª. Quando se deseja retirar um elemento que é folha da árvore (elemento que não tem filhos). Neste caso, retira o elemento da árvore e atualiza o pai, pois seu filho não existe mais. 2ª. Quando o nó a ser retirado possui um único filho. Para retirar esse elemento é necessário antes acertar o ponteiro do pai, pulando o nó: o único nó neto passa a ser filho direto. 3ª. Quando o nó a ser retirado tem dois filhos.
a) Encontrar o elemento que precede o elemento a ser retirado na ordenação. Equivale encontrar o elemento mais à direita da sub-árvore à esquerda. b) Trocar a informação do nó a ser retirado com a informação do nó encontrado. c) Retirar o nó encontrado (que contém a informação do nó que se deseja retirar). Esse procedimento deve ser seguido para não haver violação da ordenação da árvore. Exemplo da operação para retirar o elemento com informação igual a 6. Arv* retira (Arv* r, int v){ if (v == NULL) return NULL; else if (r->info > v) r->esq = retira(r->esq,v); else if (r->info < v) r->dir = retira(r->dir,v); else{ if (r->esq == NULL && r->dir == NULL){ free(r); r = NULL; else if (r->esq == NULL){ Arv* t = r; r =r->dir; free(t); else if (r->dir == NULL){ Arv* t = r; r = r->esq; free(t); else{ Arv* pai = r; Arv* f = r->esq; while (f->dir!= NULL){ pai = f; f = f->dir; r->info = f->info; f->info = v; r->esq = retira(r->esq,v); return r;
Exercícios 1. Pesquise a eficiência da operação de busca sequencial. 2. Crie um vetor de números inteiros, coloque-o em ordem crescente e faça uma busca sequencial. 3. Pesquise a eficiência da operação de busca binária. 4. Crie um vetor de números inteiros, coloque-o em ordem crescente e faça uma busca binária. 5. Escreva a função principal que utilize as funções de árvore binária de busca.