1.54k likes | 1.68k Views
Espalhamento. Sumário da Apresentação. Espalhamento A idéia inicial de espalhamento Métodos de Espalhamento Implementação das funções Hash Implementação do Espalhamento Hash com encadeamento em área separada Hash com encadeamento em área primária Hash usando Endereçamento Aberto
E N D
Sumário da Apresentação • Espalhamento • A idéia inicial de espalhamento • Métodos de Espalhamento • Implementação das funções Hash • Implementação do Espalhamento • Hash com encadeamento em área separada • Hash com encadeamento em área primária • Hash usando Endereçamento Aberto • Aplicação conta palavras (Java)
Sumário da Apresentação • Espalhamento • A idéia inicial de espalhamento • Exemplo • Métodos de Espalhamento • Método da Divisão • Método do Meio do Quadrado • Método da Multiplicação • Implementação das funções Hash • Chaves Fundamentais • Chaves de Ponto Flutuante • Chaves de Cadeias de Caracteres • Espalhamento de Objetos • Espalhamento de Containers • Associações
Sumário da Apresentação • Hash com encadeamento em área separada • Tabelas Hash Abstratas (só Java) • Encadeamento Separado • Implementação • Construtor e Destrutor (C++) • Construtor e métodos getLength e purge (Java) • Inserção e exclusão de itens • Encontro de itens • Hash com encadeamento em área primária • Tabelas de Espalhamento Encadeadas • Implementação • Construtor e Destrutor (C++) • Construtor e métodos getLength e purge (Java) • Inserção e encontro de itens • Exclusão de itens
Sumário da Apresentação • Hash usando Endereçamento Aberto • Sondagem linear • Sondagem Quadrática • Duplo Hashing • Implementação • Construtor e Destrutor • Inserção de itens • Encontro de itens • Exclusão de itens
Sumário do item Espalhamento • A idéia inicial de espalhamento • Exemplo • Métodos de Espalhamento • Método da Divisão • Método do Meio do Quadrado • Método da Multiplicação
A idéia inicial de espalhamento • Exemplo: Deseja-se implementar um container de busca que será usado para armazenar cadeias de caracteres, K K = {“ett”; “tva”, “tre”, “fyra”; “fem”; “sex”²; “aju”, “atta”; “nio”; “tio”; “elva”; “tolv”}. Considere-se a definição de uma função de espalhamento, ou “hash”, como mostrado na figura abaixo: xh(x) xh(x) "ett" 1 "sju“ 7 "två" 2 "åtta“ 8 "tre“ 3 "nio“ 9 "fyra“ 4 "tio“ 10 "fem" 5 "elva“ 11 "sex“ 6 "tolv“ 12
Espalhamento em memória Secundária • O Espalhamento em memória secundária é o processo preferencial para a implantação dos arquivos de acesso direto.
Espalhamento em Memória Secundária 1- Agrupar um determinado número de registros em uma unidade com endereço comum chamada "bucket". 2- Calcular o espaço de armazenamento necessário para o arquivo. Esse dimensionamento do arquivo depende da densidade de empacotamento ou fator de carga que é a razão entre o número de registros nos arquivos e a capacidade total dos "buckets". 3- Escolher uma função "hash" que é a transformação a aplicar à chave para obter o endereço do "bucket". 4- Optar por uma técnica de resolução do problema de transbordamento. Ocorre transbordamento ou "overflow" quando se endereça um registro a um "bucket" já cheio.
Buckets (1) • O arquivo é dividido em seções menores denominadas "buckets", que podem conter um ou mais registros. • A função "Hash" atribui a cada registro um endereço de "bucket" ("home address"), onde este pode ser acomodado. • O tamanho de “bucket” é determinado pelo número de registros que este pode armazenar, ou ainda, pelo número de "slots" que ele contém. Um "slot" é uma unidade de armazenamento que contém espaço para um registro. • Um arquivo com 1000 registros pode ser composto de 1.000 "buckets" de tamanho 1, ou 500 "buckets" de tamanho 2, etc... • Pode-se relacionar o tamanho do "bucket" a características físicas do meio de armazenamento. Logo, o tempo de transporte do “bucket” para a memória principal é proporcional ao tamanho do bucket.
Buckets (2) • Ocorre uma colisão quando durante uma inclusão de registros, dois deles têm o mesmo endereço calculado pela função “hash”. Estes registros são chamados sinônimos. As colisões não constituem problemas enquanto não se atingir a capacidade do "bucket" correspondente. A partir daí, ocorre transbordamento, que consiste no fato de um registro não poder ser acomodado em "home bucket". • Define-se como "home bucket" aquele que está associado ao "home address" do registro, que fora calculado pela função "hash" aplicada à chave do registro. • Aumentando o tamanho dos "buckets" diminui a probabilidade de transbordamento mas aumenta o tempo de busca do registro no "bucket". Contudo, o acesso à memória principal é mais rápido comparado com o tempo de busca em memória secundária, uma vez que o “bucket” será transferido para memória principal.
Método do resto da Divisão • Para este caso a função hash é h(x) = x mod M, sendo M o número de endereços disponíveis para armazenamento. • Pode-se implementar um container de busca usando um array de tamanho n=12. Para inserir o item x, basta armazena-lo na posição h(x)-1 do array. • Os elementos do conjunto K são chamados de chaves. • É usual armazenar estas chaves em um array ou arquivo. • Imediatamente pode-se utilizar esta idéia para armazenar não apenas cadeias de caracteres e sim objetos dos quais um dos atributos seja uma chave do conjunto K. • A técnica de "hashing" proporciona uma recuperação de registros extremamente rápida se comparada com a recuperação seqüencial. O tempo de recuperação em arquivos sequenciais cresce com o tamanho do arquivo, o que não ocorre com o "hashing". No projeto de um arquivo de acesso direto os fatores que é necessário considerar são os seguintes:
Método do Meio Quadrado • O espalhamento pelo meio do quadrado considera que M = 2k, o número de endereços (“buckets”), é um múltiplo de 2. Para palavras de tamanho W, com w bits as operações inteiras são feitas módulo W. • A função é hash de x é obtida elevando x ao quadrado módulo W e reduzindo este número ao domínio {0,1,...,M-1} por meio da multiplicação por dois elevado a w-k o que zera os w-k bits à esquerda. • O código que se segue mostra esta implementação unsigned int const k = 10; // M==1024 unsigned int const w = bitsizeof(unsigned int); unsigned int h(unsigned int x) { return (x * x) >> (w - k); }
Método da Multiplicação • Uma variação do método do meio do quadrado é o método da multiplicação, pelo qual hash de x não multiplica x por si mesmo e sim por uma constante a • A escolha de a deve ser tal que seja relativamente primo com W, o que se consegue achando a' tal que aa’ = 1 (mod W). Ouseja, a' é a inversa de a modulo W. • Vale a propriedade axa'=aa'x=1x. • Para palavras de 32 bits pode-se escolher a = 2.654.435.769 cuja representação binária é 10 011 110 001 101 110 111 100 110 111 001. Sua inversa é • O código que se segue mostra esta implementação unsigned int const k = 10; // M==1024 unsigned int const w = bitsizeof(unsigned int); unsigned int const a = 2654435769U; unsigned int h(unsigned int x);
Sumário do itemImplementação das funções Hash • Chaves Inteiras • Chaves de Ponto Flutuante • Chaves de Cadeias de Caracteres • Espalhamento de Objetos • Espalhamento de Containers • Associações
Fundamentos • As chaves dos objetos podem ser de quaisquer tipos de dados até estruturas como associações ou containers. • Para um conjunto de chaves, K, e uma constante positiva M, uma função hash é da forma h: K {0,1,..., M-1} • É conveniente implementar a função h como uma composição de duas funções f e g. A função f mapeia chaves em inteiros f: K Z+ aonde Z+ é o conjunto dos inteiros não negativos. A função g mapeia inteiros não negativos em {0, 1, ..., M-1}, g: Z+ {0, 1, ..., M-1} • Desde que sejam conhecidas as funções f e g, a função hash h é definida como a composição delas: h = f g • Ou seja, o valor de hash de uma chave x é dado por g(f(x)).
Implementação das funções Hash Implementação C++
Chaves Inteiras • A função f que mapeia chaves em inteiros para chaves constituídas de inteiros é f(x) = x • Definição da Função Hash de Inteiros // pgm08_01.cpp typedef unsigned int HashValue; HashValue Hash (char c) { return abs (c); } HashValue Hash (int i) { return abs (i); }
Chaves de Ponto Flutuante • Todo real não nulo x pode ser expresso da forma x = m * 2e , sendo • A função f pode ser escrita • Sendo W o maior inteiro que pode ser expresso em uma palavra do computador x e y colidirão se suas mantissas diferirem de menos de 1/2W independente do valor dos expoentes. • Definição da Função Floating-Point Hash // pgm08_02.cpp HashValue Hash (double d) { if (d == 0) return 0; else { int exponent; double mantissa = std::frexp (d, &exponent); return (2 * fabs (mantissa) - 1) * ~0U; } }
Chaves de Cadeias de Caracteres • Para chaves constituídas de cadeias de caracteres pode-se concatenar as representações dos caracteres. Sendo, por exemplo strings de comprimento 4 , estes strings podem ser transformados em inteiros pela expressão • Sendo potência de 2 pode-se escrever a função C++: HashValue Hash (string const& s) { return s[0] << 24 | s[1] << 16 | s[2] << 8 | s [3]; } • Para tratar strings de comprimento n pode-se fazer • O inconveniente é que não se está fixando limites para f(s). Pode-se corrigir isto fazendo aonde
Chaves de Cadeias de Caracteres • Sendo W e B potencias de dois o valor calculado depende apenas dos últimos W/B caracteres do string. • Para e , todos os strings que possuam os mesmos quatro caracteres colidem. • Considerando f(s) como um polinômio em B, os coeficientes do polinômio, si, podem ser calculados pela Regra de Horner, que evita o cálculo dos expoentes de x. HashValue result = 0; for (unsigned int i = 0; s [i] != 0; ++i) result = result * B + s [i]; • Como o efeito de consideração de polinômio de base 2 é semelhante ao deslocamento (shift) basta concatenar os resultados parciais
Chaves de Cadeias de Caracteres HashValue result = 0; for (unsigned int i = 0; s [i] != 0; ++i) result = result << b ^ s [i]; • Pode-se estudar os efeitos de consideração dos caracteres mais à esquerda e mais à direita ou ainda utilizar a regra do polinômio com coeficientes variáveis, por exemplo, obtidos de um array s. • Definição da Função Hash de Character String //pgm08_03.cpp unsigned int const shift = 6; HashValue const mask = ~0U << (bitsizeof (HashValue) - shift); HashValue Hash (std::string const& s) { HashValue result = 0; for (unsigned int i = 0; s [i] != 0; ++i) result = (result & mask) ^ (result << shift) ^ s [i]; return result; }
Espalhamento de Objetos • Um template da classe Wrapper é usado para envelopar instâncias de tipos de dados C++ construídos em uma interface abstrata Object. Usando o template as quatro classes Char, Int, Double, and String são declaradas da forma: typedef Wrapper<char> Char; typedef Wrapper<int> Int; typedef Wrapper<double> Double; typedef Wrapper<string> String; • Como estas classes devem ser concretas devem ter implementações de todas as funções membros, inclusive a função Hash. • Definição da função Menbro Hash da Classe Wrapper<T> //pgm08_04.cpp template <class T> HashValue Wrapper<T>::Hash () const { return ::Hash (datum); }
Espalhamento de Containers • A função hash f(c) de um container c, com n objetos, o1, o2, …, on é definida como • Para obter o hash de um container calcula-se a soma dos valores hash dos objetos contidos. • A função Container::Hash usa a função Accept para fazer um visitante especial HashingVisitor visitar todos os objetos do container. Quando visita um objeto chama a função Hash do visitado e acumula o resultado. • Definição da função Membro Hash da Classe Container
Espalhamento de Containers // pgm08_05.cpp class HashingVisitor : public Visitor { HashValue value; public: HashingVisitor (HashValue _value) : value (_value) {} void Visit (Object& object) { value += object.Hash (); } HashValue Value () const { return value; } }; HashValue Container::Hash () const { HashingVisitor visitor (::Hash (typeid (*this).name())); Accept (visitor); return visitor.Value (); }
Associações • Já que a função Accept é virtual todas as classes derivadas da classe Container fornecerão as implementações correspondentes. • Utilizando o conceito de chave pode-se escrever A = {(k,v):kK, v V} • A função hash pode ser expressa como fA(k,v) = fK(k) • Sedo fk a função hash associada ao conjunto K. • Uma Association possui dois objetos class Association : public Object, public Ownership { Object* key; Object* value; ...}; • Para definir a função hash de uma associação basta chamar a função membro hash do objeto para o qual a variável membro key aponta • DefiniçãodaFunção Hash Member daclasse Association HshValue Association::Hash() const { return key->Hash(): }
Implementação das funções Hash Implementação Java
Método hashCode da classe Int • A função f que mapeia chaves em inteiros para chaves constituídas de inteiros é f(x) = x // pgm08_01.java public class Int extends AbstractObject { protected int value; public int hashCode() { return value; } // ... }
Método hashCode da classe Dbl • A definição da função f é: // pgm08_02.java public class Dbl extends AbstractObject { protected double value; public int hashCode() { long bits = Double.doubleToLongBits(value); return (int)(bits >>> 20); } // ... }
Chaves de Cadeias de Caracteres • Para chaves constituídas de cadeias de caracteres pode-se concatenar as representações dos caracteres. Sendo, por exemplo strings de comprimento 4 , este string pode ser transformado em inteiro pela expressão • Sendo B=27potência de 2 pode-se escrever a função Java: static int f (String s) { return s.charAt(0) << 21 | s.charAt (1) << 14 | s.charAt (2) << 7 | s.charAt (3); } • Para tratar strings de comprimento n pode-se fazer • O inconveniente é que não se está fixando limites para f(s). Pode-se corrigir isto fazendo aonde
Chaves de Cadeias de Caracteres • Sendo W e B are potencias de dois o valor calculado depende apenas dos últimos W/B caracteres do string. • Para e , todos os strings que possuam os mesmos quatro caracteres colidem. • Considerando f(s) como um polinômio em B, os coeficientes do polinômio, si, podem ser calculados pela Regra de Horner, que evita o cálculo dos expoentes de x. static int f (String s) { int result = 0; for (int i = 0; i < s.length (); ++i) result = result * B + s.charAt (i); return result; } • Como o efeito de consideração de polinômio de base 2 é semelhante ao deslocamento (shift) basta concatenar os resultados parciais
Chaves de Cadeias de Caracteres static int f (String s) { int result = 0; for (int i = 0; i < s.length () ; ++i) result = result << b ^ s.charAt (i); return result; } • Pode-se estudar os efeitos de consideração dos caracteres mais à esquerda e mais à direita ou ainda utilizar a regra do polinômio com coeficientes variáveis, por exemplo, obtidos de um array s.
Método hashCode da classe Str // pgm08_03.java public class Str extends AbstractObject { protected String value; private static final int shift = 6; private static final int mask = ~0 << (32 - shift); public int hashCode() { int result = 0; for(int i = 0; i < value.length (); ++i) result = (result & mask) ^ (result << shift) ^ value.charAt (i); return result; } // ... }
Espalhamento de Containers • A função hash f(c) de um container c, com n objetos, o1, o2, …, on é definida como • Para obter o hash de um container calcula-se a soma dos valores hash dos objetos contidos.
Método hashCode da classe AbstractContainer // pgm08_04.java public abstract class AbstractContainer extends AbstractObject implements Container { protected int count; public int hashCode() { Visitor visitor = new AbstractVisitor() { private int value; public void visit (Object object) { value += object.hashCode(); } public int hashCode() { return value; } }; accept (visitor); return getClass().hashCode() + visitor.hashCode (); }// ... }
Associações • Já que a função Accept é virtual todas as classes derivadas da classe Container fornecerão as implementações correspondentes. • Utilizando o conceito de chave pode-se escrever • A função hash pode ser expressa como • Sedo fk a função hash associada ao conjunto K. • Uma Association possui dois objetos public class Association extends AbstractObject { protected Comparable key; protected Object value; ...}; • Para definir a função hash de uma associação basta chamar a função membro hash do objeto para o qual a variável key membro aponta
Método hashCode da classe Association // pgm08_05.java public class Association extends AbstractObject { protected Comparable key; protected Object value; public int hashCode() { return key.hashCode(); } // ... }
Sumário do item Implantação do Espalhamento • Hash com encadeamento em área separada • Tabelas Hash Abstratas (só Java) • Encadeamento Separado • Hash com encadeamento em área primária • Tabelas de Espalhamento Encadeadas • Hash usando Endereçamento Aberto • Sondagem linear • Sondagem Quadrática • Duplo Hashing • Implementação
Implementação do Espalhamento Implementação C++
Tabelas Hash • Definição da Classe HashTable //pgm08_07.cpp class HashTable : public virtual SearchableContainer { protected: unsigned int length; public: HashTable (unsigned int); virtual unsigned int H (Object const&) const; };
Implementação do EspalhamentoC++ Hash com encadeamento em área separada
Tabelas Hash • Definição das funções H Member e do Constructor da Classe HashTable //pgm08_08.cpp HashTable::HashTable (unsigned int _length) : length (_length) {} unsigned int HashTable::H (Object const& object) const { return object.Hash () % length; } • O construtor de HashTable recebe um simples argumento e inicializa a variável membro de maneira adequada. A função membro corresponde à composição h = g f. A função membro H recebe como argumento uma referencia constante a um objeto e retorna o resultado Hash modulo length.
Encadeamento Separado • Tabela Hash usando Encadeamento Separado
Definição da Classe ChainedHashTable // pgm08_09.cpp class ChainedHashTable : public HashTable { Array<LinkedList<Object*> > array; public: ChainedHashTable (unsigned int); // ... };
Definição das funções Purge Member, Constructor e Destructor da Classe ChainedHashTable (1) // pgm08_10.cpp ChainedHashTable::ChainedHashTable (unsigned int _length) : HashTable (_length), array (_length) {}
Definição das funções Purge Member, Constructor e Destructor da Classe ChainedHashTable (2) // pgm08_10.cpp (Continuação) void ChainedHashTable::Purge() { for (unsigned int i = 0; i < length; ++i) { if(IsOwner ()) { ListElement<Object*> const* ptr; for( ptr = array[i].Head (); ptr != 0; ptr = ptr->Next() ) delete ptr->Datum (); } array[i].Purge(); } count = 0; } ChainedHashTable::~ChainedHashTable () { Purge (); }