Uma solução é possível apenas devido à diferença entre 1 megabyte e 1 milhão de bytes. Existem cerca de 2 no poder 8093729.5 maneiras diferentes de escolher 1 milhão de números de 8 dígitos com duplicatas permitidas e solicitar sem importância, portanto, uma máquina com apenas 1 milhão de bytes de RAM não possui estados suficientes para representar todas as possibilidades. Mas 1M (menos 2k para TCP / IP) é 1022 * 1024 * 8 = 8372224 bits, portanto, uma solução é possível.
Parte 1, solução inicial
Essa abordagem precisa de um pouco mais de 1 milhão. Refino-a para caber na 1 milhão depois.
Armazenarei uma lista compacta e ordenada de números no intervalo de 0 a 99999999 como uma sequência de sublistas de números de 7 bits. A primeira sublista contém números de 0 a 127, a segunda sublista contém números de 128 a 255, etc. 100000000/128 é exatamente 781250, portanto 781250 tais sublistas serão necessários.
Cada sub-lista consiste em um cabeçalho de sub-lista de 2 bits seguido por um corpo de sub-lista. O corpo da sub-lista ocupa 7 bits por entrada da sub-lista. As sublistas são todas concatenadas juntas, e o formato permite dizer onde uma sub-lista termina e a seguinte começa. O armazenamento total necessário para uma lista totalmente preenchida é de 2 * 781250 + 7 * 1000000 = 8562500 bits, ou seja, 1,021 M-bytes.
Os 4 possíveis valores de cabeçalho da sub-lista são:
00 Sublist vazia, nada a seguir.
01 Singleton, existe apenas uma entrada na sublist e os próximos 7 bits a mantêm.
10 A sublist contém pelo menos 2 números distintos. As entradas são armazenadas em ordem não decrescente, exceto que a última entrada é menor ou igual à primeira. Isso permite que o final da sublist seja identificado. Por exemplo, os números 2,4,6 seriam armazenados como (4,6,2). Os números 2,2,3,4,4 seriam armazenados como (2,3,4,4,2).
11 A sublist contém 2 ou mais repetições de um único número. Os próximos 7 bits fornecem o número. Em seguida, vêm zero ou mais entradas de 7 bits com o valor 1, seguidas por uma entrada de 7 bits com o valor 0. O comprimento do corpo da sub-lista determina o número de repetições. Por exemplo, os números 12,12 seriam armazenados como (12,0), os números 12,12,12 seriam armazenados como (12,1,0), 12,12,12,12 seriam (12,1 1,0) e assim por diante.
Começo com uma lista vazia, leio vários números e os armazeno como números inteiros de 32 bits, classifico os novos números no local (usando heapsort, provavelmente) e os mesclamos em uma nova lista compacta. Repita até que não haja mais números para ler e, em seguida, caminhe na lista compacta mais uma vez para gerar a saída.
A linha abaixo representa a memória imediatamente antes do início da operação de mesclagem de lista. Os "O" s são a região que contém os números inteiros de 32 bits classificados. Os "X" s são a região que contém a lista compacta antiga. Os sinais "=" são a sala de expansão da lista compacta, 7 bits para cada número inteiro nos "O" s. Os "Z" s são outros custos indiretos.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
A rotina de mesclagem começa a ler no "O" mais à esquerda e no "X" à esquerda e começa a escrever no "=" mais à esquerda. O ponteiro de gravação não captura o ponteiro de leitura da lista compacta até que todos os novos números inteiros sejam mesclados, porque os dois ponteiros avançam 2 bits para cada sublist e 7 bits para cada entrada na lista compacta antiga, e há espaço extra suficiente para o Entradas de 7 bits para os novos números.
Parte 2, colocando-a em 1 milhão
Para espremer a solução acima em 1M, preciso tornar o formato da lista compacta um pouco mais compacto. Vou me livrar de um dos tipos de sub-listas, para que existam apenas três valores possíveis diferentes do cabeçalho da sub-lista Então eu posso usar "00", "01" e "1" como os valores do cabeçalho da sublist e salvar alguns bits. Os tipos de sub-listas são:
Uma sublist vazia, nada a seguir.
B Singleton, existe apenas uma entrada na sublist e os próximos 7 bits a mantêm.
C A sublist contém pelo menos 2 números distintos. As entradas são armazenadas em ordem não decrescente, exceto que a última entrada é menor ou igual à primeira. Isso permite que o final da sublist seja identificado. Por exemplo, os números 2,4,6 seriam armazenados como (4,6,2). Os números 2,2,3,4,4 seriam armazenados como (2,3,4,4,2).
DA sub-lista consiste em 2 ou mais repetições de um único número.
Meus três valores de cabeçalho de sub-lista serão "A", "B" e "C", portanto, preciso de uma maneira de representar sublistas do tipo D.
Suponha que eu tenha o cabeçalho da sub-lista do tipo C seguido por 3 entradas, como "C [17] [101] [58]". Isso não pode fazer parte de uma sub-lista válida do tipo C, conforme descrito acima, pois a terceira entrada é menor que a segunda, mas maior que a primeira. Eu posso usar esse tipo de construção para representar uma sub-lista do tipo D. Em termos de bits, em qualquer lugar que eu tenha "C {00 ?????} {1 ??????} {01 ?????}" é uma sub-lista do tipo C impossível. Vou usar isso para representar uma sub-lista que consiste em 3 ou mais repetições de um único número. As duas primeiras palavras de 7 bits codificam o número (os bits "N" abaixo) e são seguidas por zero ou mais {0100001} palavras seguidas por uma palavra {0100000}.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
Isso deixa apenas listas que contêm exatamente 2 repetições de um único número. Eu representarei aqueles com outro padrão impossível de sub-lista do tipo C: "C {0 ??????} {11 ?????} {10 ?????}". Há muito espaço para os 7 bits do número nas 2 primeiras palavras, mas esse padrão é maior que a sub-lista que ele representa, o que torna as coisas um pouco mais complexas. Os cinco pontos de interrogação no final podem ser considerados não parte do padrão, então eu tenho: "C {0NNNNNN} {11N ????} 10" como meu padrão, com o número a ser repetido armazenado no "N "s. São 2 bits a mais.
Vou precisar emprestar 2 bits e pagá-los dos 4 bits não utilizados nesse padrão. Ao ler, ao encontrar "C {0NNNNNN} {11N00AB} 10", imprima 2 instâncias do número nos "N" s, substitua o "10" no final pelos bits A e B e rebobine o ponteiro de leitura por 2 bits. Leituras destrutivas são aceitáveis para esse algoritmo, pois cada lista compacta é percorrida apenas uma vez.
Ao escrever uma sub-lista de 2 repetições de um único número, escreva "C {0NNNNNN} 11N00" e defina o contador de bits emprestados como 2. A cada gravação em que o contador de bits emprestados é diferente de zero, é decrementado para cada bit gravado e "10" é escrito quando o contador atinge zero. Portanto, os próximos 2 bits gravados serão inseridos nos slots A e B e, em seguida, o "10" será colocado no final.
Com três valores de cabeçalho de sub-lista representados por "00", "01" e "1", posso atribuir "1" ao tipo de sub-lista mais popular. Vou precisar de uma pequena tabela para mapear os valores do cabeçalho da sub-lista para os tipos de sub-lista e de um contador de ocorrências para cada tipo de sub-lista, para que eu saiba qual é o melhor mapeamento de cabeçalho da sub-lista.
A pior representação mínima possível de uma lista compacta totalmente preenchida ocorre quando todos os tipos de sub-listas são igualmente populares. Nesse caso, economizo 1 bit para cada 3 cabeçalhos de sub-listas; portanto, o tamanho da lista é 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083,3 bits. Arredondar até um limite de palavras de 32 bits, 8302112 bits ou 1037764 bytes.
1M menos os 2k para o estado TCP / IP e os buffers são 1022 * 1024 = 1046528 bytes, deixando-me 8764 bytes para brincar.
Mas e o processo de alteração do mapeamento do cabeçalho da sublist? No mapa de memória abaixo, "Z" é sobrecarga aleatória, "=" é espaço livre, "X" é a lista compacta.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Comece a ler no "X" mais à esquerda e comece a escrever no "=" mais à esquerda e trabalhe à direita. Quando terminar, a lista compacta será um pouco menor e estará no final errado da memória:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
Então, eu vou precisar desviar para a direita:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
No processo de alteração do mapeamento de cabeçalho, até 1/3 dos cabeçalhos da sublist serão alterados de 1 para 2 bits. Na pior das hipóteses, todos estarão no topo da lista, portanto, precisarei de pelo menos 781250/3 bits de armazenamento gratuito antes de iniciar, o que me leva de volta aos requisitos de memória da versão anterior da lista compacta: (
Para contornar isso, dividirei as sublistas 781250 em 10 grupos de sub-listas de 78125 sublistas cada. Cada grupo possui seu próprio mapeamento de cabeçalho de sublist independente. Usando as letras A a J para os grupos:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
Cada grupo de sub-listas diminui ou permanece o mesmo durante uma alteração no mapeamento do cabeçalho da sub-lista:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
O pior caso de expansão temporária de um grupo de sub-listas durante uma alteração de mapeamento é 78125/3 = 26042 bits, abaixo de 4k. Se eu permitir 4k mais os 1037764 bytes para uma lista compacta totalmente preenchida, isso me deixa 8764 - 4096 = 4668 bytes para os "Z" s no mapa de memória.
Isso deve ser suficiente para as 10 tabelas de mapeamento de cabeçalho da sub-lista, as 30 contagens de ocorrência do cabeçalho da sub-lista e os outros poucos contadores, ponteiros e pequenos buffers que eu precisarei e o espaço que usei sem perceber, como espaço de pilha para endereços de retorno de chamada de função e variáveis locais.
Parte 3, quanto tempo levaria para ser executado?
Com uma lista compacta vazia, o cabeçalho da lista de 1 bit será usado para uma sublist vazia e o tamanho inicial da lista será 781250 bits. Na pior das hipóteses, a lista aumenta 8 bits para cada número adicionado; portanto, são necessários 32 + 8 = 40 bits de espaço livre para que cada um dos números de 32 bits seja colocado no topo do buffer da lista e, em seguida, classificado e mesclado. Na pior das hipóteses, alterar o mapeamento do cabeçalho da sublist resulta em um uso de espaço de 2 * 781250 + 7 * entradas - 781250/3 bits.
Com uma política de alterar o mapeamento de cabeçalho da sub-lista após cada quinto mesclado, quando houver pelo menos 800000 números na lista, uma execução de pior caso envolveria um total de cerca de 30 milhões de atividades compactas de leitura e gravação de lista.
Fonte:
http://nick.cleaton.net/ramsortsol.html