armazenamento eficiente de números primos
-
06-07-2019 - |
Pergunta
Para uma biblioteca, eu preciso armazenar os primeiros números primos até um limite L. Esta coleção deve ter um O (1) tempo de pesquisa (para verificar se um número é primo ou não) e deve ser fácil, dado um número, para encontrar o próximo número primo (assumindo que é menor do que L).
Tendo em conta que L é fixo, uma Eratostene peneira para gerar a lista é muito bem. Agora, eu uso uma matriz booleana embalado para armazenar a lista, que contém apenas entradas para números ímpares entre 3 e L (inclusive). Isso leva (L-2) / 2 bits de memória. Eu gostaria de ser capaz de aumentar estaticamente L sem usar mais memória.
Existe uma estrutura de dados usando menos memória com propriedades semelhantes? Ou pelo menos com o tempo de pesquisa constante? (Números ímpares podem ser enumerados até chegarmos a uma prime)
(a língua que eu escrevi isso em é Fator mas esta questão seria a mesma em qualquer língua que tem built-in ou facilmente programável bit embalado matrizes)
Solução
Você pode verificar explicitamente números mais privilegiados para a redundância de remoção.
No momento que você faz isso só para dois, verificando divisibilidade por dois explícita e, em seguida, armazenar apenas para números ímpares se eles são primos.
Para 2 e 3 você começa restos de 0 a 5, sendo que apenas 1 e 5 não são divisíveis por dois ou três e pode levar a um número primo, então você está para baixo a 1/3.
Para 2, 3 e 5, você obter 8 números fora de 30, o que é bom para armazenar em um byte.
Isto é explicado em mais detalhes aqui .
Outras dicas
Uma alternativa para os mapas de bits e rodas embalados - mas igualmente eficazes em certos contextos - é armazenar as diferenças entre primos consecutivos. Se você deixar de fora o número 2, como de costume, em seguida, todas as diferenças são ainda. Armazenar diferença / 2 você pode obter até 2 ^ 40ish regiões (pouco antes 1999066711391), utilizando variáveis ??de tamanho de byte.
Os números primos até 2 ^ 32 requerem apenas 194 Mbytes, em comparação com 256 Mbyte para um probabilidades somente bitmap embalado. Iteração sobre primos armazenados-delta é muito mais rápido do que para o armazenamento de rodas, que inclui a roda de módulo-2 conhecido como mapa de bits probabilidades-only.
Para intervalos de 1999066711391 em diante, o tamanho da célula grande ou armazenamento de comprimento variável são necessários. O último pode ser extremamente eficiente, mesmo se esquemas muito simples são utilizados (por exemplo, manter-se adicionar até um byte <255 tenha sido adicionado, como em LZ4 estilo de compressão), por causa da extremamente baixa frequência de lacunas mais de 510/2.
Por razões de eficiência, é melhor dividir a faixa em seções (páginas) e gerenciá-los estilo B-Tree.
Entropy-codificar as diferenças (Huffmann ou aritméticas Coding) corta os requisitos de armazenamento permanente para um pouco menos de metade, que fica perto do ideal teórico e melhor do que listas ou rodas comprimidos usando os melhores packers disponíveis.
Se os dados são armazenados descompactado, em seguida, ainda é muito mais compacto do que arquivos de números binários ou de texto, por uma ordem de magnitude ou mais. Com um índice de estilo B-Tree no lugar, é fácil simplesmente mapear seções na memória quando necessário e interagir sobre eles em uma velocidade impressionante.
No momento você está tratando 2 como caso especial e, em seguida, ter um array onde cada número ímpar é mapeado para um elemento na matriz (com alguns números ímpares sendo prime). Pode-se melhorar este por tratamento 2 e 3 como casos especiais, reconhecendo que o resto dos números primos são na forma 6n + 1 ou 6n-1 (que é para todos os primos p onde p> 3, p mod 6 = 1 ou 5). Isto pode ser mais generalizada - veja Wikipedia . Para todos os números primos p> 5, p mod 30 = 1, 7, 11, 13, 17, 19, 23 ou 29. Você poderia continuar com isso e reduzir a memória necessária à custa do tempo de processamento (embora ainda será O (1), apenas um O mais lento (1)).
Talvez um trie estrutura de dados que contém apenas os números primos é o que você está procurando . Em vez de usar personagens como índices você poderia usar os dígitos inteiros. Uma implementação desta são Judy-matriz s.
Altough, eles não atender às suas O (1) exigência, eles são extremamente eficiente para a memória para chaves semelhantes (como a maioria de partes de números são) e muito rápido para procurar com um O (m) (m = chave- comprimento) no máximo.
Se você olhar para cima para um primo na árvore de pré-gerado, você pode andar a árvore até encontrá-lo ou você já está no nó que está ao lado do precedente e seguinte prime.
Dado que a memória é tão barato, eu não acho que você pode fazer muito melhor a partir de uma perspectiva de velocidade do que o seu esquema existente.
Se há uma solução melhor, então eu diria que ele iria aproveitar a Prime Number Theorem que mostra que, como L se torna maior, o limite de
p (L) / (L / ln (L)) se aproxima de 1.
Talvez uma solução melhor seria ter uma solução de embalagem adaptativa em uma estrutura de dados como uma espécie de lista Skip .
Como sobre algum tipo de tabela hash?
Você precisaria de uma boa função hash (algo como n mod p
, onde p
não é um múltiplo de qualquer um dos q
mais baixos números primos - escolha q
suficientemente elevado, a fim de minimizar o número de colisões).
Como sobre uma árvore Intervalo? http://www.geeksforgeeks.org/interval-tree/
Pode não ser O (1), mas é muito rápido. Como talvez O (log (p (n))) em que p (n) é o número de números primos até o número n. Desta forma, você vai a memória que você precisa será proporcional ao número de apenas primos, cortando muito o custo de memória.
Por exemplo, suponha que você encontrar um primo em p1 palavra a dizer e, em seguida, o próximo a p2, Inserir intervalo (p1, p2) e assim por diante e quando você executa uma busca por qualquer número nesse intervalo que vai voltar este intervalo e você pode retornar p2 qual seria a resposta no seu caso.
Se você pode descobrir quais são Mersenne ou outros números primos facilmente representados, você pode ser capaz de salvar alguns pedaços usando a representação com uma bandeira para os números aplicáveis.
Além disso, como sobre o armazenamento dos números como a diferença entre o número anterior? Em seguida, o tamanho não deve subir tão rápido (mas lookup seria lenta). Combinando com a abordagem acima, você pode armazenar números primos de Mersenne e a diferença a partir do último Primo de Mersenne.
Verifique o tutorial TopCoder em números primos: http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=math_for_topcoders