Para que tipo de dados são as operações da tabela de hash O (1)?

18

Das respostas para (Quando) a consulta de tabela de hash O (1)? , Concluí que as tabelas de hash têm pior comportamento, pelo menos amortizado, quando os dados satisfazem certas condições estatísticas e existem técnicas para ajudar a tornar essas condições amplas.O(1)

No entanto, da perspectiva de um programador, não sei de antemão quais serão meus dados: geralmente vêm de alguma fonte externa. E eu raramente tenho todos os dados de uma só vez: geralmente inserções e exclusões acontecem a uma taxa que não fica muito abaixo da taxa de pesquisas, portanto, o pré-processamento dos dados para ajustar a função hash acaba.

Então, dando um passo à frente: dado algum conhecimento sobre a fonte de dados, como posso determinar se uma tabela de hash tem chance de ter operações e possivelmente quais técnicas usar na minha função de hash?O(1)

Gilles 'SO- parar de ser mau'
fonte
Ah, e as tabelas Hash versus árvores binárias estão relacionadas, mas aqui estou focando nas tabelas de hash e quando elas estão (ou não estão) no seu melhor.
Gilles 'SO- stop be evil'
O melhor caso para qualquer função hash é quando os dados são distribuídos uniformemente.
0x0
@ Sunil: Não é verdade. Você pode ter funções de hash personalizadas.
Raphael
Eu acho que essa pergunta é muito ampla. Em particular, você pode concretizar como seria o conhecimento sobre fontes de dados?
Raphael
@Raphael Por exemplo, se as chaves são cadeias de caracteres: nomes de pessoas, nomes de arquivos em um diretório, tags XML, hashes de arquivos,…
Gilles 'SO- para de ser mau' '

Respostas:

4

Existem várias técnicas que garantem que as pesquisas sempre exijam operações O (1), mesmo no pior caso.

Como posso determinar se uma tabela de hash tem chance de ter operações O (1) e possivelmente quais técnicas usar na minha função de hash?

O pior caso ocorre quando algum invasor mal-intencionado (Mallory) deliberadamente fornece dados que Mallory selecionou especificamente para tornar o sistema lento.

Depois de escolher uma função hash específica, é provavelmente otimista demais supor que Mallory nunca descobrirá qual função hash você escolheu. Depois que Mallory descobrir qual função de hash você escolheu, se você permitir que Mallory lhe forneça muitos dados a serem inseridos em sua tabela de hash usando essa função de hash, você estará condenado: Mallory pode gerar rapidamente internamente bilhões de itens de dados, hash-os com o seu A função hash para encontrar quais itens de dados provavelmente colidirão e, em seguida, fornecerá milhões de itens de dados que podem colidir, resultando em pesquisas que são muito mais lentas que O (1).

Todas as técnicas que garantem "pesquisas O (1) mesmo nos piores casos" evitam esse problema, fazendo um pouco de trabalho extra em cada inserção para garantir que, no futuro, todas as pesquisas possíveis tenham êxito no tempo O (1) . Em particular, assumimos (no pior caso) que Mallory descobrirá, mais cedo ou mais tarde, qual função de hash estamos usando; mas ele só tem a chance de inserir alguns itens de dados antes de escolher uma função de hash diferente - hash de tabulação ou algum outro hash universal - um que selecionamos especialmente para que todos os dados que temos até agora possam ser pesquisados ​​em 2 ou 3 sondas - ou seja, O (1). Como selecionamos essa função aleatoriamente, podemos ter certeza de que Mallory não saberá qual função escolhemos por um tempo. Mesmo se Malloryimediatamente nos fornece dados que, mesmo com essa nova função de hash, colidem com dados anteriores, podemos escolher outra nova função de hash, de modo que, após a revisão, todos os dados anteriores que ele e todos os outros nos forneceram agora possam ser visualizados em 2 ou 3 sondas no pior caso - ou seja, O (1) pesquisas no pior caso.

É bastante fácil selecionar aleatoriamente uma nova função de hash e repetir toda a tabela com frequência suficiente para garantir que cada pesquisa seja sempre O (1). Embora isso garanta que cada pesquisa seja sempre O (1), essas técnicas, ao inserir o item N em uma tabela de hash que já contém itens N-1, ocasionalmente podem exigir tempo O (N) para essa inserção. No entanto, é possível projetar o sistema de forma que, mesmo quando Mallory deliberadamente forneça novos dados que, usando a nova função hash, colidem com dados anteriores, o sistema possa aceitar muitos itens de Mallory e outros antes de precisar fazer uma reconstrução total de O (N). As técnicas de tabela de hash que selecionam uma nova função e refazer a tarefa para garantir O (1) pesquisas, mesmo no pior caso, incluem:

  • O cuckoo hashing garante que cada pesquisa de chave seja bem-sucedida com no máximo 2 cálculos de hash e 2 pesquisas de tabela.
  • O hashing de amarelinha garante que cada pesquisa de chave seja bem-sucedida após a inspeção no pequeno número H (talvez H = 32) de entradas consecutivas na tabela.
  • hashing perfeito dinâmico - o artigo de 1994 de Dietzfelbinger é o primeiro que li que apontou que, embora seja repetido "com frequência" para garantir que cada pesquisa-chave sempre tenha sucesso com 2 cálculos de hash e 2 pesquisas, é possível para realizar uma revisão completa tão raramente que, embora cada revisão completa use O (n) tempo, o custo médio esperado de inserções e exclusões é O (1) amortizado.

Estruturas de dados / tabelas de hash

David Cary
fonte
5

A pesquisa da tabela de hash sempre pode ser para conjuntos estáticos; consulte o artigo de 2002 de Arne Andersson e Mikkel Thorup: conjuntos ordenados dinâmicos com árvores de pesquisa exponenciaisO(1)

Primeiramente, fornecemos o primeiro algoritmo determinístico de tempo polinomial (em n) para a construção de um dicionário estático de espaço linear com custo de acesso no pior caso (cf. hash perfeito). Como mencionado anteriormente, uma estrutura linear de dados espaciais que suporta consultas de membros (consultas vizinhas não são suportadas) em tempo constante pode ser construída com o pior custo sem divisão [30]. Mostramos que a dependência do tamanho da palavra pode ser removida.O ( n 2 W )O(1)O(n2W)

No caso geral, Andersson et al fornecem um algoritmo para estruturas de dados indexadas por hash que suportam pesquisas e atualizações em . Além disso, eles provam que esse limite é ótimo. Portanto, sabemos exatamente o quão perto podemos chegar de no caso geral.O(1)O(logn/loglogn)O(1)

AT
fonte
5

Não sou especialista em estruturas de dados, mas a abordagem teórica usual para o hash é que se defina uma família de funções (por exemplo, ) e, em seguida, considere o comportamento em um na pior das hipóteses, um membro da família escolhido aleatoriamente , onde o adversário não conhece a escolha aleatória com antecedência. Isso é semelhante à forma como os algoritmos aleatórios são analisados ​​também: a expectativa é assumida sobre as escolhas do algoritmo, não a distribuição de entrada.ha,b(x)=ax+bmodp

No passado, de acordo com um artigo da Usenix de Crosby e Wallach , as linguagens de programação comuns não faziam nada assim, deixando muitos aplicativos da Web (e outros servidores) abertos a um ataque de DoS com base em colisões de fabricação. (O artigo é de 2003, mas sugere que Dan Bernstein havia descoberto a mesma idéia um pouco antes).

Uma pesquisa rápida no Google afirma que o estado da arte em termos de implementações melhorou e não melhorou .

Outro aspecto é que, em um mundo de grande largura de banda, os ataques de tempo dificultam a localização de colisões online (em vez de offline, como sugere o link Crosby-Wallach). Parece que me lembro que Daniel Golovin teve resultados alguns anos atrás em estruturas de dados que não são vulneráveis ​​a ataques de tempo, mas não sei se eles são amplamente utilizados.

Louis
fonte
0

A análise de casos médios para as tabelas de hash é feita sob o pressuposto usual de uniformidade das entradas, o que antes ocorre devido à navalha do occam.

Se você tiver conhecimento adicional sobre o domínio e a distribuição das chaves, poderá fazer a mesma análise de caso médio e substituir a distribuição uniforme por sua distribuição e recalcular as expectativas, pelo menos em teoria.

Obviamente, a dificuldade decorre do fato de que análises não uniformes de casos de avaérage 'são difíceis de resolver. E seu "conhecimento" pode não ser convenientemente expressável como uma distribuição que pode ser usada facilmente nessa análise.

Obviamente, a coisa mais fácil de fazer são simulações. Implemente as tabelas de hash e observe como elas se saem para o seu conjunto típico de entradas.

uli
fonte
8
Eu tenho que discordar da primeira frase. A suposição padrão é que a função hash é aleatória, não os dados de entrada. Supondo que dados distribuídos uniformemente empurram a análise para o reino da fantasia - dados do mundo real nunca são uniformes! Mas existem técnicas de livros didáticos para tornar as funções de hash suficientemente uniformes. Veja hash universal e especificamente hash de tabulação .
22412 JeffE
@JeffE Veja a análise de caso médio na resposta de Raphael, que afirma essa suposição de uniformidade. Você não pode fazer uma análise de caso médio sem uma distribuição. Você tem que escolher um e se não for dado, a navalha do occam sugere o uniforme.
213 uli
6
Claro que você tem uma distribuição; é a distribuição que você usa para escolher a função hash. Escolher uma distribuição para os dados de entrada é como procurar suas chaves perdidas sob o poste de luz; claro, a luz está melhor, mas provavelmente não foi onde você as deixou cair.
19412 JeffE
@JeffE É assim que uma análise de caso médio é feita, escolha uma distribuição e comece a calcular. Como sempre, a escolha da distribuição é discutível. Você pode fazer uma análise de caso médio não uniforme.
191 uli
4
Sim, eu sei como é feito. (Verifique meu perfil.) Se você deseja que sua análise seja preditiva (que é o ponto principal da análise), você deve aleatoriamente a função hash. Então você sabe a distribuição precisa, porque você a escolheu.
21412 JeffE
-1

Permutações (de comprimento fixo), como um caso específico de conjuntos finitos conhecidos: é relativamente fácil atribuir números únicos às permutações, como neste artigo . Eu usei isso (em uma implementação um pouco menos horrível) para mapear permutações de comprimento em uma matriz de tamanho. Mas eu poderia fazer isso porque acabaria precisando de toda permutação; se você estiver usando apenas um subconjunto, precisará de uma função personalizada para esse subconjunto ou de uma matriz esparsa eficiente.n !nn!

isturdy
fonte