Estou trabalhando na tabela de hash na linguagem C e testando a função de hash para string.
A primeira função que tentei é adicionar código ascii e usar o módulo (% 100), mas obtive resultados ruins com o primeiro teste de dados: 40 colisões por 130 palavras.
Os dados finais de entrada conterão 8.000 palavras (é um arquivo armazenado em um arquivo). A tabela de hash é declarada como tabela int [10000] e contém a posição da palavra em um arquivo txt.
A primeira pergunta é qual é o melhor algoritmo para hash de string? e como determinar o tamanho da tabela de hash?
desde já, obrigado !
:-)
Respostas:
Eu tive bons resultados com
djb2
Dan Bernstein.fonte
size_t
ou outro valor não assinado (como o não assinado por muito tempo neste código). O chamador é responsável por receber o módulo do resultado para ajustá-lo à tabela de hash. O chamador controla o slot da tabela que está sendo hash; não é a função. Ele apenas retorna um número não assinado.Primeiro, você geralmente não deseja usar um hash criptográfico para uma tabela de hash. Um algoritmo que é muito rápido pelos padrões criptográficos ainda é terrivelmente lento pelos padrões da tabela de hash.
Segundo, você deseja garantir que cada bit da entrada possa / irá afetar o resultado. Uma maneira fácil de fazer isso é girar o resultado atual por um número de bits e, em seguida, XOR o código de hash atual com o byte atual. Repita até chegar ao final da string. Observe que geralmente você também não deseja que a rotação seja um múltiplo múltiplo do tamanho de bytes.
Por exemplo, supondo o caso comum de bytes de 8 bits, você pode girar 5 bits:
Editar: Observe também que 10000 slots raramente são uma boa opção para um tamanho de tabela de hash. Você geralmente deseja uma de duas coisas: você quer um número primo como o tamanho (necessário para garantir a correção com alguns tipos de resolução de hash) ou então uma potência de 2 (portanto, reduzir o valor para o intervalo correto pode ser feito com um simples máscara de bits).
fonte
A Wikipedia mostra uma boa função de hash de string chamada Jenkins, uma de cada vez. Ele também cita versões aprimoradas desse hash.
fonte
Existem várias implementações de hashtable existentes para C, da biblioteca padrão C hcreate / hdestroy / hsearch, às da APR e glib , que também fornecem funções de hash pré-construídas. Eu recomendo usá-las em vez de inventar sua própria função de hashtable ou hash; eles foram altamente otimizados para casos de uso comuns.
Se seu conjunto de dados é estático, no entanto, sua melhor solução é provavelmente usar um hash perfeito . O gperf irá gerar um hash perfeito para você para um determinado conjunto de dados.
fonte
O djb2 possui 317 colisões para este dicionário de inglês de 466k, enquanto o MurmurHash não possui nenhum para hashes de 64 bits e 21 para hashes de 32 bits (cerca de 25 é esperado para 466k hashes aleatórios de 32 bits). Minha recomendação é usar o MurmurHash, se disponível, é muito rápido, pois leva vários bytes por vez. Mas se você precisar de uma função hash simples e curta para copiar e colar no seu projeto, eu recomendo o uso de murmúrios na versão de um byte por vez:
O tamanho ideal de uma tabela de hash é - em resumo - o maior possível, enquanto ainda se encaixa na memória. Como geralmente não sabemos ou queremos pesquisar quanta memória temos disponível, e pode até mudar, o tamanho ideal da tabela de hash é aproximadamente o dobro do número esperado de elementos a serem armazenados na tabela. Alocar muito mais do que isso tornará sua tabela de hash mais rápida, mas com retornos cada vez menores, tornando sua tabela de hash menor que isso, tornando-a exponencialmente mais lenta. Isso ocorre porque existe um trade-off não linear entre complexidade de espaço e tempo para tabelas de hash, com um fator de carga ideal de 2-sqrt (2) = 0,58 ... aparentemente.
fonte
Primeiro, 40 colisões para 130 palavras com hash para 0..99 são ruins? Você não pode esperar o hash perfeito se não estiver tomando medidas específicas para que isso aconteça. Uma função hash comum não terá menos colisões do que um gerador aleatório na maioria das vezes.
Uma função de hash com boa reputação é MurmurHash3 .
Finalmente, em relação ao tamanho da tabela de hash, isso realmente depende do tipo de tabela de hash que você tem em mente, especialmente se os buckets são extensíveis ou de um slot. Se os buckets forem extensíveis, novamente existe uma opção: você escolhe o comprimento médio do bucket para as restrições de memória / velocidade que possui.
fonte
n - m * (1 - ((m-1)/m)^n) = 57.075...
. 40 colisões são melhores do que o que poderia ser esperado por acaso (46 a 70 com um p-score de 0,999). A função hash em questão é mais uniforme do que se fosse aleatória ou estamos testemunhando um evento muito raro.Embora
djb2
, como apresentado no stackoverflow da cnicutar , seja quase certamente melhor, acho que vale a pena mostrar os hashes K&R também:1) Aparentemente, um terrível algoritmo de hash, como apresentado na 1ª edição da K&R ( fonte )
2) Provavelmente, um algoritmo de hash bastante decente, como apresentado na versão 2 da K&R (verificado por mim na p. 144 do livro); Nota: certifique-se de remover
% HASHSIZE
da instrução de retorno se você planeja fazer o módulo dimensionar para o comprimento da matriz fora do algoritmo hash. Além disso, eu recomendo que você faça o retorno e o tipo "hashval" emunsigned long
vez do simplesunsigned
(int).Observe que, pelos dois algoritmos, fica claro que um dos motivos pelo qual o hash da 1ª edição é tão terrível é porque NÃO leva em consideração a ordem dos caracteres da string , portanto
hash("ab")
, retornaria o mesmo valor quehash("ba")
. Isto não é acontece com o hash da 2ª edição, no entanto, que (muito melhor!) Retornaria dois valores diferentes para essas strings.As funções de hash do GCC C ++ 11 usadas para
unordered_map
(um modelo de tabela de hash) eunordered_set
(um modelo de conjunto de hash) parecem ser as seguintes.Código:
fonte
Eu tentei essas funções de hash e obtive o seguinte resultado. Eu tenho cerca de 960 ^ 3 entradas, cada uma com 64 bytes de comprimento, 64 caracteres em ordem diferente, valor de hash 32 bits. Códigos daqui .
Uma coisa estranha é que quase todas as funções de hash têm taxa de colisão de 6% para meus dados.
fonte
Uma coisa que usei com bons resultados é a seguinte (não sei se já foi mencionado porque não me lembro o nome).
Você pré-calcula uma tabela T com um número aleatório para cada caractere no alfabeto da sua chave [0,255]. Você faz o hash da sua chave 'k0 k1 k2 ... kN', usando T [k0] xor T [k1] xor ... xor T [kN]. Você pode mostrar facilmente que isso é tão aleatório quanto o seu gerador de números aleatórios e é computacionalmente viável. Se você realmente encontrar uma instância muito ruim com muitas colisões, poderá repetir tudo usando um novo lote de números aleatórios.
fonte