pesquisa em memória primária - wordpress.com · 2019. 11. 20. · bruno hott 3 introdução –...
TRANSCRIPT
Pesquisa em Memória Primária
Bruno HottAlgoritmos e Estruturas de Dados IDECSI – UFOP
Bruno Hott 2
Pesquisa em Memória Primária
● Introdução - Conceitos Básicos
● Pesquisa Sequencial
● Pesquisa Binária
● Árvores de Pesquisa
– Árvores Binárias de Pesquisa sem Balanceamento
– Árvores Binárias de Pesquisa com Balanceamento
– Árvores AVL
– Árvores Digitais
– Árvores Vermelho e Preto
● Transformação de Chave (Hashing)
– Listas Encadeadas,
– Endereçamento Aberto
Bruno Hott 3
Introdução – Conceitos Básicos
● Estudo de como recuperar informação a partir de uma grande massa de informação previamente armazenada.
● A informação é dividida em registros.
● Cada registro possui uma chave para ser usada na pesquisa.
● Objetivo da pesquisa: Encontrar uma ou mais ocorrências de registros com chaves iguais à chave de pesquisa.
● Pesquisa com sucesso
X Pesquisa sem sucesso.
Bruno Hott 4
Introdução – Conceitos Básicos
● Tabelas
– Conjunto de registros ou arquivos TABELAS⇒ TABELAS
– Tabela: associada a entidades de vida curta, criadas na memória interna durante a execução de um programa.
– Arquivo: geralmente associado a entidades de vida mais longa, armazenadas em memória externa.
Bruno Hott 5
Escolha do Método de Pesquisa mais Adequado a uma Determinada Aplicação
● Depende principalmente:
– Quantidade dos dados envolvidos.
– Arquivo estar sujeito a inserções e retiradas frequentes
● Se conteúdo do arquivo é estável é importante minimizar o tempo de pesquisa, sem preocupação com o tempo necessário para estruturar o arquivo
Bruno Hott 6
Algoritmos de PesquisaTipos Abstratos de Dados
● É importante considerar os algoritmos de pesquisa como tipos abstratos de dados (TADs), 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ções.
● Operações mais comuns:
(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 Juntar dois arquivos para formar um arquivo maior.
Bruno Hott 7
Dicionário
● Nome comumente utilizado para descrever uma estrutura de dados para pesquisa.
● Dicionário é um tipo abstrato de dados com as operações:
(1 Inicializa
(2 Pesquisa
(3 Insere
(4 Retira
● Analogia com um dicionário da língua portuguesa:
– Chaves palavras
– Registros entradas associadas com*pronúncia, definição, sinônimos, outras informações
Bruno Hott 8
Pesquisa Sequencial
● Método de pesquisa mais simples:
– a partir do primeiro registro, pesquise seqüencialmente até encontrar a chave procurada; então pare.
● Armazenamento de um conjunto de registros por meio do tipo estruturado arranjo
Bruno Hott 9
Pesquisa Sequencial
#define MAX 10
typedef struct{ long Chave; /* outros componentes */}TRegistro;
typedef struct{ TRegistro Item[MAX+1]; int n;}TTabela;
Bruno Hott 10
Pesquisa Sequencial
//Implementação para as operações Inicializa, Pesquisa:
void inicializa(TTabela *T{){ T->n = 0;}
int pesquisa_seq(TTabela *T, long x{){ int i; T->Item[0].chave = x; i = T->n; while(T->Item[i].chave != x{) i--; return i;}
Bruno Hott 11
Pesquisa Sequencial
//Implementação para a operacao Insere:
void Insere(Registro Reg, TTabela *T{){ if(T->n == MAX{) printf("Erro : tabela cheia\n"{); else{ T->n++; T->Item[T->n] = Reg; }}
Bruno Hott 12
Pesquisa Sequencial
● Pesquisa retorna o índice do registro que contém a chave x;
● Caso não esteja presente, o valor retornado é zero.
● A implementação não suporta mais de um registro com uma mesma chave.
● Para aplicações com esta característica é necessário incluir um argumento a mais na função Pesquisa para conter o índice a partir do qual se quer pesquisar.
Bruno Hott 13
Pesquisa Sequencial
● Utilização de um registro sentinela na posição zero do array:
– Garante que a pesquisa sempre termina: se o índice retornado por Pesquisa for zero, a pesquisa foi sem sucesso.
– Não é necessário testar se i > 0, devido a isto:● o anel interno da função Pesquisa é extremamente simples: o índice i é
decrementado e a chave de pesquisa é comparada com a chave que está no registro.
● isto faz com que esta técnica seja conhecida como pesquisa sequencial rápida.
Bruno Hott 14
Pesquisa Sequencial:Análise
● Pesquisa com sucesso:
– melhor caso : C(n) = 1
– pior caso : C(n) = n
– caso médio: C(n) = (n + 1) / 2
● Pesquisa sem sucesso:
– C (n) = n + 1.
● O algoritmo de pesquisa sequencial é a melhor escolha para o problema de pesquisa em tabelas com até 25 registros.
Bruno Hott 15
Pesquisa Binária
● Pesquisa em tabela pode ser mais eficiente se registros forem mantidos em ordem
● Para saber se uma chave está presente na tabela
(1 Compare a chave com o registro que está na posição do meio da tabela.
(2 Se a chave é menor então o registro procurado está na primeira metade da tabela
(3 Se a chave é maior então o registro procurado está na segunda metade da tabela.
(4 Repita o processo até que a chave seja encontrada, ou fique apenas um registro cuja chave é diferente da procurada, significando uma pesquisa sem sucesso.
Bruno Hott 16
Exemplo de Pesquisa Binária para a Chave G
Bruno Hott 17
Pesquisa Binária
int pesquisa_binaria(TTabela *T, long x{){ int i, esq, dir;
if(T->n == 0{) return 0;
esq = 1; dir = T->n; do{ i = (Esq + Dir{) / 2; if(x > T->Item[i].Chave{) esq = i+1; else dir = i-1; while( (x != T->Item[i].Chave{) && (esq <= dir{) {);
if(x == T->Item[i].chave{) return i; else return 0;
}
Bruno Hott 18
Pesquisa Binária:Análise
● A cada iteração do algoritmo, o tamanho da tabela é dividido ao meio.
● Logo: o número de vezes que o tamanho da tabela é dividido ao meio é cerca de log n.
● Ressalva: o custo para manter a tabela ordenada é alto: a cada inserção na posição p da tabela implica no deslocamento dos registros a partir da posição p para as posições seguintes.
● Consequentemente, a pesquisa binária não deve ser usada em aplicações muito dinâmicas.
Bruno Hott 19
Árvores de Busca
Bruno HottAlgoritmos e Estruturas de Dados IDECSI – UFOP
Bruno Hott 20
Árvores de Pesquisa
● A árvore de pesquisa é uma estrutura de dados muito eficiente para armazenar informação.
● Particularmente adequada quando existe necessidade de considerar todos ou alguma combinação de:
(1 Acesso direto e sequencial eficientes.
(2 Facilidade de inserção e retirada de registros.
(3 Boa taxa de utilização de memória.
(4 Utilização de memória primária e secundária.
Bruno Hott 21
Árvores Binárias de Pesquisa sem Balanceamento
● Para qualquer nó que contenha um registro
● Temos a relação invariante:
– Todos os registros com chaves menores estão na subárvore à esquerda.
– Todos os registros com chaves maiores estão na subárvore à direita.
Bruno Hott 22
Árvores Binárias de Pesquisasem Balanceamento
● O nível do nó raiz é 0.
– Se um nó está no nível i então a raiz de suas subárvores estão no nível i+1.
– A altura de um nó é o comprimento do caminho mais longo deste nó até um nó folha.
– A altura de uma árvore é a altura do nó raiz.
Bruno Hott 23
TAD-Árvore Binária de Pesquisa
typedef struct{ long chave; /* outros componentes */}TRegistro;
typedef struct TNo{ TRegistro reg; Tno *pEsq, *pDir;}TNo;
typedef TNo* TDicionario;
Bruno Hott 24
Procedimento para Pesquisar na Árvore
● Para encontrar um registro com uma chave x:
(1 Compare-a com a chave que está na raiz.
(2 Se x é menor, vá para a subárvore esquerda.
(3 Se x é maior, vá para a subárvore direita.
(4 Repita o processo recursivamente, até que a chave procurada seja encontrada ou um nó folha é atingido.
(5 Se a pesquisa tiver sucesso então o conteúdo do registro retorna no próprio registro x.
Bruno Hott 25
Procedimento para Pesquisar na Árvore
int pesquisa(TNo* pRaiz, TRegistro *pX{){ if(pRaiz == NULL{) return 0;
if(pX->chave < pRaiz->reg.chave{) return pesquisa(pRaiz->pEsq,pX{); if(pX->chave > pRaiz->reg.chave{) return pesquisa(pRaiz->pDir,pX{);
/* if(pX->chave == pRaiz->reg.chave{) */ *pX = pRaiz->reg; return 1;}
Bruno Hott 26
Procedimento para Pesquisar na Árvore(não recursivo)
int pesquisa(TNo* pRaiz, TRegistro *pX{){ TNo* pAux = pRaiz;
while(pAux != NULL{){ if(pX->chave == pAux->reg.chave{){ *pX = pAux->Reg; return 1;
if (pX->chave > pAux->reg.chave{) pAux = pAux->pDir; else /* if (pX->chave < pAux->reg.chave{) */ pAux = pAux->pEsq; } return 0;}
Bruno Hott 27
Procedimento para Inserir na Árvore
● Onde inserir?
– Atingir um apontador nulo em um processo de pesquisa significa uma pesquisa sem sucesso.
– O apontador nulo atingido é o ponto de inserção.
● Como inserir?
– Cria célula contendo registro
– Procura lugar na árvore
– Se registro não tiver na árvore, insere-o
Bruno Hott 28
Procedimento para Inserir na Árvore
int insere(TNo** ppRaiz, TRegistro x{){ if(*ppRaiz == NULL{){ *ppRaiz = cria_no(x{); return 1; }
if(x.chave < (*ppRaiz{)->reg.chave{) return insere(&((*ppRaiz{)->pEsq{), x{); if(x.chave > (*ppRaiz{)->reg.chave{) return insere(&((*ppRaiz{)->pDir{), x{); return 0; /* elemento jah existe */}
Bruno Hott 29
Procedimento para Inserir na Árvore(não recursivo)
int insere(TNo** ppRaiz, TRegistro x{){ TNo** ppAux = ppRaiz;
while (*ppAux != NULL{){ if(x.chave < (*ppAux{)->reg.Chave{) ppAux = &((*ppAux{)->pEsq{); else if(x.chave > (*ppAux{)->reg.chave{) ppAux = &((*ppAux{)->pDir{); else /* if(x.chave == (*ppAux{)->reg.chave{) */ return 0; }
*ppAux = cria_no(x{); return 1;}
Bruno Hott 30
Procedimentos para Inicializar a Árvore
void inicializa(TNo** Dicionario{){ *TDicionario = NULL;}
Bruno Hott 31
Retirada de um Registro da Árvore
● Como retirar?
● Folha?
● Nó interno, contendo um filho?
● Raiz da árvore ou subárvore?
Bruno Hott 32
Procedimento para Retirar x da Árvore
● A retirada de um registro não é tão simples quanto a inserção.
● Se o nó que contém o registro a ser retirado possui no máximo um descendente, a operação é simples.
● No caso do nó conter dois descendentes o registro a ser retirado deve ser primeiro:
– substituído pelo registro mais à direita na subárvore esquerda;
– ou pelo registro mais à esquerda na subárvore direita.
Bruno Hott 33
Exemplo de Retirada de um Registro da Árvore
● para retirar o registro com chave 5 da árvore basta trocá-lo pelo registro com chave 4 ou pelo registro com chave 6, e então retirar o nó que recebeu o registro com chave 5.
Bruno Hott 34
Exemplo de Retirada de um Registro da Árvore
int retira(Registro x, Tno** p{){ TNo* pAux;
if(*p == NULL{) return 0; if(x.chave < (*p{)->reg.chave{) return retira(x, &(*p{)->pEsq{); if(x.chave > (*p{)->reg.chave{) return retira(x, &(*p{)->pDir{);
/* if(x == (*p{){) */ if((*p{)->pDir == NULL{){ pAux = *p; *p = (*p{)->pEsq; free(pAux{); return 1; } if((*p{)->pEsq == NULL{){ pAux = *p; *p = (*p{)->pDir; free(pAux{); return 1; }
/* dois filhos */ antecessor(*p, &(*p{)->pEsq{); return 1;}
Bruno Hott 35
Exemplo de Retirada de um Registro da Árvore
void antecessor(TNo* q, TNo** r{){ TNo* pAux; if( (*r{)->pDir != NULL{){ antecessor(q, &(*r{)->pDir{); return; } q->reg = (*r{)->reg; pAux = *r; *r = (*r{)->pEsq; free(pAux{);}
Bruno Hott 36
Exemplo de Retirada de um Registro da Árvore
Obs.: proc. recursivo Antecessor só é ativado quando o nó que contém registro a ser retirado possui 2 descendentes.
Solução usada por Wirth, 1976, p.211.
Bruno Hott 37
Caminhamento Central
Percorrer a árvore:
Em que ordem chaves são recuperadas (usando caminhamento central)?
1, 2, 3, 4, 5, 6 e 7.
Bruno Hott 38
Caminhamento Central
● Após construída a árvore, pode ser necessário percorrer todo os registros que compõem a tabela ou arquivo.
● Existe mais de uma ordem de caminhamento em árvores, mas a mais útil é a chamada ordem de caminhamento central.
● Uma característica importante do caminhamento central é que os nós são visitados de forma ordenada.
Bruno Hott 39
Caminhamento Central
void central(TNo* pRaiz{){ if (pRaiz == NULL{) return;
central(p->pEsq{); printf("%ld\n", p->reg.chave{); central(p->pDir{);}
Bruno Hott 40
Análise
● O número de comparações em uma pesquisa com sucesso:
– melhor caso: C(n) = O(1)
– pior caso: C(n) = O(n)
– caso médio: C(n) = O(log n)
● O tempo de execução dos algoritmos para árvores binárias de pesquisa dependem muito do formato das árvores.
Bruno Hott 41
Análise
● Para obter o pior caso basta que as chaves sejam inseridas em ordem crescente ou decrescente. Neste caso a árvore resultante é uma lista linear, cujo número médio de comparações é (n + 1)/2.
● Para uma árvore de pesquisa aleatória o número esperado de comparações para recuperar um registro qualquer é cerca de 1,39 log n, apenas 39% pior que a árvore completamente balanceada.
Bruno Hott 42
Árvores AVL
Bruno HottAlgoritmos e Estruturas de Dados IDECSI – UFOP
Bruno Hott 43
Árvore AVL
● Árvore binária de busca tal que, para qualquer nó interno v, a diferença das alturas dos filhos de v é no máximo 1.
● Árvores AVL são balanceadas
88
44
17 78
32 50
48 62
2
4
1
1
2
3
1
1
Bruno Hott 44
Árvores Binárias Balanceadas e AVL
● Inserindo os nós 30, 20, 40, 10, 25, 35 e 50 nesta ordem, teremos:
30
20
10 25
40
35 50
Bruno Hott 45
Árvores Binárias Balanceadas e AVL
● Inserindo os nós 10, 20, 30, 40 e 50 nesta ordem, teremos:
10
20
30
40
50
Bruno Hott 46
Árvores Binárias Balanceadas
● Existem ordens de inserção de nós que conservam o balanceamento de uma árvore binária.
● Na prática é impossível prever essa ordem ou até alterá-la.
Bruno Hott 47
Árvores Binárias Balanceadas
● A vantagem de uma árvore balanceada com relação a uma degenerada está em sua eficiência.
● Por exemplo: numa árvore binária degenerada de 1.000.000 nós são necessárias, em média, 500.000 comparações (semelhança com arrays ordenados e listas encadeadas).
● Numa árvore balanceada com o mesmo número de nós essa média reduz-se a 20 comparações.
Bruno Hott 48
AVL
● Algoritmo de balanceamento de árvores binárias.
● A origem da denominação AVL vem dos seus dois criadores: Adel’son-Vel’skii e Landis.
● Ano de divulgação: 1962.
Bruno Hott 49
Árvore AVL (TAD)
typedef struct{ long chave; /* outros componentes */}TRegistro;
typedef struct{ TRegistro reg; TNo* pEsq, pDir;}TNo;
typedef TNo* TipoDicionario;
Bruno Hott 50
Árvores AVL
● Uma árvore binária balanceada é aquela na qual, para cada nó, as alturas de suas sub-árvores esquerda e direita diferem de, no máximo, 1.
● Fator de balanceamento (FB) de um nó é a diferença entre a altura da sub-árvore esquerda em relação à sub-árvore direita.
FB(p) = altura(sub-árvore esquerda de p)
- altura(sub-árvore direita de p)
● Em uma árvore binária balanceada todos os FB de todos os nós estão no intervalo -1 FB 1
Bruno Hott 51
FB e Altura
int fb(TNo* pRaiz{){ if(pRaiz == NULL{) return 0;
return altura(pRaiz->pEsq{) - altura(pRaiz->pDir{);}
int altura(TNo* pRaiz{){ int hEsq,hDir; if(pRaiz == NULL{) return 0;
hEsq = altura(pRaiz->pEsq{); hDir = altura(pRaiz->pDir{);
if(hEsq > hDir{) return hEsq+1; else return hDir+1;}
Bruno Hott 52
AVL
● Inicialmente inserimos um novo nó na árvore normalmente.
● A inserção deste pode degenerar a árvore.
● A restauração do balanceamento é feita através de rotações na árvore no nó “pivô”.
● Nó “pivô” é aquele que após a inserção possui fator de balanceamento fora do intervalo.
Bruno Hott 53
AVL
● Rotação simples para a direita (RSD)
– FB > 1 (subárvore esquerda maior que subárvore direita)
– E a subárvore esquerda desta subárvore esquerda é maior que a subárvore direita dela
– Então realizar uma rotação simples para a direita.
3
2
1
Bruno Hott 54
AVL
● Rotação simples para a direita (RSD)
3
2
1
2
1 3
Bruno Hott 55
AVL
● Rotação simples para a esquerda (RSE)
– FB < -1 (subárvore esquerda menor que subárvore direita)
– E a subárvore direita desta subárvore direita é maior que a subárvore esquerda dela
– Então realizar uma rotação simples para a esquerda.
1
2
3
Bruno Hott 56
AVL
● Rotação simples para a esquerda (RSE)
1
2
3
2
1 3
Bruno Hott 57
AVL
● Rotação dupla para a direita (rotação esquerda-direita - RED)
– FB > 1 (subárvore esquerda maior que subárvore direita)
– E a subárvore esquerda desta subárvore esquerda é menor ou igual que a subárvore direita dela
– Então realizar uma rotação dupla para a direita.
1
3
2
Bruno Hott 58
AVL
● Rotação dupla para a direita (rotação esquerda-direita - RED)
1
3
2
2
1 3
3
2
1
Bruno Hott 59
AVL
● Rotação dupla para a esquerda (rotação direita-esquerda RDE)
FB < -1 (subárvore esquerda menor que subárvore direita)
– E a subárvore direita desta subárvore direita é menor que a subárvore esquerda dela
– Então realizar uma rotação dupla para a esquerda.
Bruno Hott 60
AVL
● Rotação dupla para a esquerda (rotação direita-esquerda RDE)
1
3
2
1
2
3
2
1 3
Bruno Hott 61
Rotações Simples
void rse(TNo** ppRaiz{){ TNo *pAux;
pAux = (*ppRaiz{)->pDir; (*ppRaiz{)->pDir = pAux->pEsq; pAux->pEsq = (*ppRaiz{); (*ppRaiz{) = pAux;}
void rsd(TNo** ppRaiz{){ /* para casa */}
Bruno Hott 62
Rotações Duplas
int balanca_direita(TNo** ppRaiz{ int fbd = fb((*ppRaiz{)->pDir{); if(fbd < 0{){ rse(ppRaiz{); return 1; else if(fbd > 0{){ /* rotacao dupla esquerda */ rsd(&((*ppRaiz{)->pDir{){); rse(ppRaiz{); /* &(*ppRaiz{) */ return 1; } return 0;}
int balanca_esquerda(TNo** ppRaiz{){ /* para casa */}
Bruno Hott 63
Balanceamento
int balanceamento(TNo** ppRaiz{){ int fb = fb(*ppRaiz{); if(fb > 1{) return balanca_esquerda(ppRaiz{); else if (fb < -1{) return balanca_direita(ppRaiz{); else return 0;}
Bruno Hott 64
Inserção em uma Árvore AVL
● Inserção como em uma árvore binária de pesquisa
● Sempre feita expandindo um nó externo. Exemplo:
Bruno Hott 65
Reestruturação Trinodo
● x, y, z (filho, pai e avô) renomeados como a,b,c (percurso interfixado)
● rotações levam b para o topo
Caso 1: rotação simples à esquerda
Caso 2: rotação dupla à esquerda
(outros dois casos são simétricos)
Bruno Hott 66
Exemplo de inserção (cont.)
88
44
17 78
32 50
48 62
2
5
1
1
3
4
2
1
54
1
T0 T2
T3
x
y
z
2
34
5
67
1
T1
88
44
17
7832 50
48
622
4
1
1
2 2
3
154
1
T 0 T1
T2
T3
x
y z
123
4
5
6
7
Desbalanceado
Balanceado
Bruno Hott 67
Inserção
int insere(TNo** ppRaiz, TRegistro* x{){ if (*ppRaiz == NULL{){ *ppRaiz=(Tno*{)malloc(sizeof(TNo{){); (*ppRaiz{)->reg = *x; (*ppRaiz{)->pEsq = NULL; (*ppRaiz{)->pDir = NULL; return 1; }else if((*ppRaiz{)->reg.chave > x->chave{){ if(insere(&(*ppRaiz{)->pEsq,x{){){ if(balanceamento(ppRaiz{){) return 0; else return 1; } }
else if((*ppRaiz{)->reg.chave < x->chave{){ if(insere(&(*ppRaiz{)->pDir, x{){){ if(balanceamento(ppRaiz{) return 0; else return 1; }else return 0; }else return 0; /* valor já presente */}
Bruno Hott 68
Implementação de Inserção
● Cálculo de fatores de balanceamento
– Custo: O(log n) ??
● Como melhorar?
– Cada nó: ● Fator de balanceamento● Profundidade x Altura
● Problema: atualizar dados durante rotações
Bruno Hott 69
Remoção em uma árvore AVL
● Remoção em uma árvore binária de busca pode causar desbalanceamento. Exemplo:
Bruno Hott 70
Rebalanceamento após uma remoção
● Seja z o primeiro nó desbalanceado encontrado acima de w. Seja y o filho de z com maior altura, e x o filho de y com maior altura.
● Executar restructure(x) para rebalancear z.
● Pode ocorrer desbalanceamento de outro nó acima, continuar verificação de balanceamento até à raiz.
Bruno Hott 71
Remoção
int remove (TNo** ppRaiz, Registro* pX{){ if(*ppRaiz == NULL{) return 0; else if ((*ppRaiz{)->reg.chave == pX->chave{){ *pX = (*ppRaiz{)->reg; antecessor(ppRaiz, &((*ppRaiz{)->pEsq{){); balanceamento(ppRaiz{); return 1; }else if((*ppRaiz{)->reg.chave > pX->chave{){ if(remove((*ppRaiz{)->pEsq, pX{){){ balanceamento(ppRaiz{); return 1; }else return 0; }else /* código para sub-árvore direita */}
Bruno Hott 72
Tempos de execução para árvores AVL
● uma única reestruturação é O(1)
– usando uma árvore binária implementada
– com estrutura ligada
● pesquisa é O(log n)
– altura de árvore é O(log n), não necesita reestruturação
● inserir é O(log n)
– busca inicial é O(log n)
– reestruturação para manter balanceamento é O(log n)
● remove é O(log n)
– busca inicial é O(log n)
– reestruturação para manter balanceamento é O(log n)
Bruno Hott 73
Verificação
int eh_arvore_avl(TNo* pRaiz{){ int fb; if(pRaiz == NULL{) return 1;
if(!eh_arvore_avl(pRaiz->pEsq{){) return 0; if(!eh_arvore_avl(pRaiz->pDir{){) return 0;
fb = fb(pRaiz{); if( (fb > 1{) || (fb < -1{) {) return 0; else return 1;}