Procurando uma implementação definida com pouco espaço na memória

9

Estou procurando a implementação do tipo de dados definido. Ou seja, temos que

  • mantenha um subconjunto dinâmico S (de tamanho n ) do universo U={0,1,2,3,,u1} de tamanho u com
  • operações insert(x)(adicione um elemento xa S ) e find(x)(verifica se o elemento xé um membro de S ).

Eu não me importo com outras operações. Para orientação, em aplicações estou trabalhando com temos você1010 .

Conheço implementações que fornecem ambas as operações no tempo O(1 1) , então me preocupo principalmente com o tamanho da estrutura de dados. Espero bilhões de entradas, mas quero evitar trocar o máximo possível.

Estou disposto a sacrificar o tempo de execução, se necessário. Tempo de execução amortizado de O(registron) é o que posso admitir; os tempos de execução esperados ou em não são admissíveis.ω(registron)

Uma idéia que tenho é que, se puder ser representado como uma união de intervalos , poderemos economizar no tamanho do armazenamento com o preço de alguma diminuição no desempenho. Além disso, alguns outros padrões de dados são possíveis, como .S[xmin, xmax][0, 2, 4, 6]

Você poderia me indicar estruturas de dados que podem fazer algo assim?

HEKTO
fonte
Como o número de elementos entra na imagem? Ou seja, o que acontece se um elemento for inserido e já houver n ? nn
vonbrand
@vonbrand - né o tamanho do conjunto S. Ele pode aumentar a cada insert, ou pode permanecer o mesmo se o elemento xjá estiver no conjunto.
HEKTO 30/01
3
Você pode aceitar uma pequena probabilidade de falsos positivos? Nesse caso, um filtro de bloom pode ser o ideal: en.wikipedia.org/wiki/Bloom_filter
Joe
11
@AlekseyYakovlev, a taxa de falsos positivos de um filtro de bloom não tem nada a ver com o tamanho do universo (apenas com o número de funções de hash , o tamanho da estrutura de dados m e o número de itens n ), mas se n realmente for próximo a u (digamos u = n c para uma pequena constante c ), você será pressionado a fazer melhor do que um vetor de bits simples, penso, com apenas c n bits totais de espaço. kmnnvocêu=ncccn
Joe

Respostas:

8

A resposta de Joe é extremamente boa e fornece todas as palavras-chave importantes.

Você deve estar ciente de que a pesquisa sucinta da estrutura de dados ainda está em um estágio inicial e muitos dos resultados são amplamente teóricos. Muitas das estruturas de dados propostas são bastante complexas de implementar, mas a maior parte da complexidade se deve ao fato de que você precisa manter a complexidade assintótica tanto no tamanho do universo quanto no número de elementos armazenados. Se um deles for relativamente constante, grande parte da complexidade desaparece.

Se a coleção for semi-estática (ou seja, as inserções são raras ou, pelo menos, de baixo volume), certamente vale a pena considerar uma estrutura de dados estática fácil de implementar (o sdarray de Sadakane é uma ótima opção) em conjunto com uma atualização cache. Basicamente, você registra atualizações em uma estrutura de dados tradicional (por exemplo, árvore B, árvore, tabela de hash) e atualiza periodicamente em massa a estrutura de dados "principal". Essa é uma técnica muito popular na recuperação de informações, pois os índices invertidos têm muitas vantagens na pesquisa, mas são difíceis de atualizar no local. Se for esse o caso, informe-me em um comentário e alterarei esta resposta para fornecer algumas dicas.

Se as inserções são mais frequentes, sugiro hash sucinto. A idéia básica é direta o suficiente para explicar aqui, então farei isso.

Portanto, o resultado teórico da informação básica é que, se você estiver armazenando elementos de um universo de u itens, e não houver outras informações (por exemplo, nenhuma correlação entre os elementos), precisará logar ( unubits para armazená-lo. (Todos os logaritmos são de base 2, a menos que especificado de outra forma.) Vocêprecisa detantos bits. Não há maneira de contornar isso.registro(vocên)+O(1 1)

Agora, alguma terminologia:

  • Se você possui uma estrutura de dados que pode armazenar os dados e apoiar suas operações no bits de espaço, chamamos isso deestrutura de dadosimplícita.registro(vocên)+O(1 1)
  • Se você possui uma estrutura de dados que pode armazenar os dados e apoiar suas operações no bits de espaço, chamamos isso de umaestrutura de dadoscompacta. Observe que, na prática, isso significa que a sobrecarga relativa (relativa ao mínimo teórico) está dentro de uma constante. Pode ser 5% de sobrecarga, 10% de sobrecarga ou 10 vezes de sobrecarga.registro(vocên)+O(registro(vocên))=(1 1+O(1 1))registro(vocên)
  • Se você possui uma estrutura de dados que pode armazenar os dados e apoiar suas operações no bits de espaço, chamamos isso deestrutura de dadossucinta.log(un)+o(log(un))=(1+o(1))log(un)

A diferença entre sucinto e compacto é a diferença entre pequeno-oh e grande-oh. Ignorando o valor absoluto por um momento ...

  • significa que existe uma constante c e um número n 0 tal que para todos n > n 0 , g ( n ) < c f ( n ) .g(n)=O(f(n))cn0n>n0g(n)<cf(n)
  • significa que para todas as constantes c existe um número n 0 tal que para todas as n > n 0 , g ( n ) < c f ( n ) .g(n)=o(f(n))cn0n>n0g(n)<cf(n)

Informalmente, big-oh e little-oh estão "dentro de um fator constante", mas com big-oh a constante é escolhida para você (pelo criador do algoritmo, pelo fabricante da CPU, pelas leis da física ou o que for), mas com pouco -oh, você mesmo escolhe a constante e pode ser tão pequena quanto quiser . Em outras palavras, com estruturas de dados sucintas, a sobrecarga relativa fica arbitrariamente pequena à medida que o tamanho do problema aumenta.

Obviamente, o tamanho do problema pode ter que ser enorme para perceber a sobrecarga relativa que você deseja, mas não pode ter tudo.

OK, com isso em nossos cintos, vamos colocar alguns números sobre o problema. Vamos supor que as chaves sejam números inteiros de bits (portanto, o tamanho do universo é 2 n ) e queremos armazenar 2 m desses números inteiros. Suponhamos que possamos organizar magicamente uma tabela de hash idealizada com ocupação total e sem desperdício, para que precisemos de exatamente 2 m de slots de hash.n2n2m2m

Uma operação de pesquisa faria o hash da chave bits, mascararia m bits para encontrar os slots de hash e depois verificaria se o valor na tabela correspondia à chave. Por enquanto, tudo bem.nm

Essa tabela de hash usa bits. Podemos fazer melhor que isso?n2m

Suponha que a função hash seja invertível. Então não precisamos armazenar a chave inteira em cada slot de hash. A localização do slot de hash fornece m bits do valor de hash; portanto, se você armazenou apenas os n - m bits restantes, poderá reconstruir a chave a partir dessas duas informações (a localização do slot de hash e o valor armazenado lá). Então você precisaria apenas ( n - m ) de 2 m de bits de armazenamento.hmnm(nm)2m

Se é pequeno em comparação com 2 n , a aproximação de Stirling e um pouco de aritmética (a prova é um exercício!) Revela que:2m2n

(nm)2m=log(2n2m)+o(log(2n2m))

Portanto, essa estrutura de dados é sucinta.

No entanto, existem duas capturas.

O primeiro problema é a construção de funções hash invertíveis "boas". Felizmente, isso é muito mais fácil do que parece; os criptografadores fazem funções invertíveis o tempo todo, apenas eles os chamam de "cifras". Você pode, por exemplo, basear uma função hash em uma rede Feistel, que é uma maneira direta de construir funções hash invertíveis a partir de funções hash não invertíveis.

O segundo problema é que tabelas de hash reais não são ideais, graças ao paradoxo do aniversário. Portanto, você deseja usar um tipo mais sofisticado de tabela de hash que o aproxima da ocupação total sem derramar. O hash do cuco é perfeito para isso, pois permite que você se aproxime arbitrariamente do ideal na teoria e na prática.

O hash do cuco exige várias funções de hash e exige que os valores nos slots de hash sejam marcados com a qual a função de hash foi usada. Portanto, se você usar quatro funções de hash, por exemplo, precisará armazenar mais dois bits em cada slot de hash. Isso ainda é sucinto à medida que cresce, por isso não é um problema na prática e ainda é melhor do que armazenar chaves inteiras.m

Ah, você também pode querer olhar para as árvores de van Emde Boas.

MAIS PENSAMENTOS

Se estiver em algum lugar perto de vocên , depoisfaça logon ( uu2 é aproximadamenteu, então (mais uma vez) assumindo que não há mais correlação entre os valores, você basicamente não pode fazer nada melhor do que um vetor de bits. Você observará que a solução de hash acima se degenera efetivamente nesse caso (você acaba armazenando um bit por slot de hash), mas é mais barato usar a chave como endereço do que usar uma função de hash.log(un)u

Se estiver muito próximo de u , toda a literatura sucinta das estruturas de dados recomenda que você inverta o sentido do dicionário. Armazene os valores que não ocorrem no conjunto. No entanto, agora você precisa efetivamente oferecer suporte à operação de exclusão e, para manter um comportamento sucinto, também precisa reduzir a estrutura de dados à medida que mais elementos são "adicionados". Expandir uma tabela de hash é uma operação bem compreendida, mas contratá-la não é.nu

Pseudônimo
fonte
Olá, quanto ao segundo parágrafo da sua resposta - espero que todas as ligações insertsejam acompanhadas por uma ligação findcom o mesmo argumento. Então, se os findretornos true, simplesmente pulamos o insert. Assim, a frequência das findchamadas é mais do que a frequência das insertchamadas, também quando nse aproxima ue as insertchamadas se tornam muito raras.
HEKTO 5/02/2014
Mas você espera que chegue perto de n eventualmente? un
Pseudônimo
No mundo real, n está crescendo até chegar a você, no entanto, não podemos prever se isso acontecerá ou não. A estrutura de dados deve funcionar bem para qualquer umn <= u
HEKTO
Direita. Então é justo dizer que não conhecemos uma única estrutura de dados que seja sucinta (no sentido acima) e que alcance isso em toda a faixa de . Eu acho que você vai querer uma estrutura de dados esparsos quandon<u, em seguida, mudar para uma densa um (por exemplo, um pouco vetor) quandoné em torno deunun<un , uma estrutura de dados esparsa com um sentido invertido quandonestiver próximo deu. u2nu
Pseudônimo
5

Parece que você deseja uma estrutura de dados sucinta para o problema de associação dinâmica .

Lembre-se de que uma estrutura de dados sucinta é aquela para a qual o requisito de espaço é "próximo" ao limite inferior da teoria da informação, mas, diferentemente de uma estrutura de dados compactada, ainda permite consultas eficientes.

O problema da associação é exatamente o que você descreve na sua pergunta:

SnU={0,1,2,3,,u1}u

  • find(x)xS
  • insert(x)xS
  • delete(x)xS

Se apenas a findoperação for suportada, esse é o problema estático da associação. Se um insertou deletefor suportado, mas não ambos, será chamado semi-dinâmico e, se todas as três operações forem suportadas, será chamado de problema de associação dinâmica .

Tecnicamente, acho que você pediu apenas uma estrutura de dados para o problema de associação semi-dinâmica, mas não conheço nenhuma estrutura de dados que tire proveito dessa restrição e também atenda a seus outros requisitos. No entanto, tenho a seguinte referência:

No Teorema 5.1 do artigo Associação em Tempo Constante e Espaço Quase Mínimo , Brodnik e Munro fornecem o seguinte resultado:

O(B)

B=log(un)

A idéia básica é que eles dividam recursivamente o universo em faixas de tamanhos cuidadosamente escolhidos, de modo que isso soa até como as técnicas podem estar na mesma linha em que você está pensando.

un

Joe
fonte
11
O resumo em papel de Brodnik & Munro não diz nada sobre inserções. Mas o resultado deles é o que podemos esperar, certo? Se n = u/2, o espaço necessário é máximo.
HEKTO 31/01
@AlekseyYakovlev Eles realmente não mencionam o caso dinâmico em abstrato, mas o teorema que lida com o caso dinâmico é citado na minha resposta (da seção 5).
Joe