Una solución solo es posible debido a la diferencia entre 1 megabyte y 1 millón de bytes. Hay alrededor de 2 en el poder 8093729.5 formas diferentes de elegir 1 millón de números de 8 dígitos con duplicados permitidos y pedidos sin importancia, por lo que una máquina con solo 1 millón de bytes de RAM no tiene suficientes estados para representar todas las posibilidades. Pero 1M (menos 2k para TCP / IP) es 1022 * 1024 * 8 = 8372224 bits, por lo que es posible una solución.
Parte 1, solución inicial
Este enfoque necesita un poco más de 1M, lo refinaré para que encaje en 1M más tarde.
Almacenaré una lista ordenada compacta de números en el rango de 0 a 99999999 como una secuencia de sublistas de números de 7 bits. La primera sublista contiene números del 0 al 127, la segunda sublista contiene números del 128 al 255, etc. 100000000/128 es exactamente 781250, por lo que se necesitarán 781250 de tales sublistas.
Cada sublista consta de un encabezado de sublista de 2 bits seguido de un cuerpo de sublista. El cuerpo de la sublista ocupa 7 bits por entrada de sublista. Todas las sublistas se concatenan juntas, y el formato permite saber dónde termina una sublista y dónde comienza la siguiente. El almacenamiento total requerido para una lista completamente poblada es 2 * 781250 + 7 * 1000000 = 8562500 bits, que es aproximadamente 1.021 M-bytes.
Los 4 posibles valores de encabezado de sublista son:
00 Sublista vacía, no sigue nada.
01 Singleton, solo hay una entrada en la sublista y los siguientes 7 bits la retienen.
10 La sublista contiene al menos 2 números distintos. Las entradas se almacenan en orden no decreciente, excepto que la última entrada es menor o igual que la primera. Esto permite identificar el final de la sublista. Por ejemplo, los números 2,4,6 se almacenarían como (4,6,2). Los números 2,2,3,4,4 se almacenarían como (2,3,4,4,2).
11 La sublista contiene 2 o más repeticiones de un solo número. Los siguientes 7 bits dan el número. Luego, ingrese cero o más entradas de 7 bits con el valor 1, seguido de una entrada de 7 bits con el valor 0. La longitud del cuerpo de la sublista dicta el número de repeticiones. Por ejemplo, los números 12,12 se almacenarían como (12,0), los números 12,12,12 se almacenarían como (12,1,0), 12,12,12,12 serían (12,1 , 1,0) y así sucesivamente.
Comienzo con una lista vacía, leo un montón de números y los almaceno como números enteros de 32 bits, clasifico los nuevos números en su lugar (probablemente usando un montón) y luego los combino en una nueva lista compacta ordenada. Repita hasta que no haya más números para leer, luego recorra la lista compacta una vez más para generar la salida.
La siguiente línea representa la memoria justo antes del inicio de la operación de fusión de lista. Las "O" son la región que contiene los enteros ordenados de 32 bits. Las "X" son la región que contiene la antigua lista compacta. Los signos "=" son la sala de expansión para la lista compacta, 7 bits para cada entero en las "O". Las "Z" son otros gastos indirectos al azar.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
La rutina de combinación comienza a leer en el extremo izquierdo "O" y en el extremo izquierdo "X", y comienza a escribir en el extremo izquierdo "=". El puntero de escritura no captura el puntero de lectura de la lista compacta hasta que se fusionen todos los enteros nuevos, porque ambos punteros avanzan 2 bits para cada sublista y 7 bits para cada entrada en la lista compacta anterior, y hay suficiente espacio adicional para el Entradas de 7 bits para los nuevos números.
Parte 2, metiéndolo en 1M
Para exprimir la solución anterior en 1M, necesito hacer que el formato de lista compacta sea un poco más compacto. Me desharé de uno de los tipos de sublista, de modo que solo haya 3 posibles valores de encabezado de sublista diferentes. Entonces puedo usar "00", "01" y "1" como los valores del encabezado de la sublista y guardar algunos bits. Los tipos de sublista son:
Una sublista vacía, nada sigue.
B Singleton, solo hay una entrada en la sublista y los siguientes 7 bits la retienen.
C La sublista contiene al menos 2 números distintos. Las entradas se almacenan en orden no decreciente, excepto que la última entrada es menor o igual que la primera. Esto permite identificar el final de la sublista. Por ejemplo, los números 2,4,6 se almacenarían como (4,6,2). Los números 2,2,3,4,4 se almacenarían como (2,3,4,4,2).
D La sublista consta de 2 o más repeticiones de un solo número.
Mis 3 valores de encabezado de sublista serán "A", "B" y "C", por lo que necesito una forma de representar sublistas de tipo D.
Supongamos que tengo el encabezado de sublista de tipo C seguido de 3 entradas, como "C [17] [101] [58]". Esto no puede ser parte de una sublista de tipo C válida como se describe anteriormente, ya que la tercera entrada es menor que la segunda pero más que la primera. Puedo usar este tipo de construcción para representar una sublista de tipo D. En términos de bits, en cualquier lugar que tenga "C {00 ?????} {1 ??????} {01 ?????}" es una sublista de tipo C imposible. Usaré esto para representar una sublista que consta de 3 o más repeticiones de un solo número. Las primeras dos palabras de 7 bits codifican el número (los bits "N" a continuación) y van seguidas de cero o más palabras {0100001} seguidas de una palabra {0100000}.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
Eso solo deja listas que contienen exactamente 2 repeticiones de un solo número. Representaré a aquellos con otro patrón de sublista de tipo C imposible: "C {0 ??????} {11 ?????} {10 ?????}". Hay mucho espacio para los 7 bits del número en las primeras 2 palabras, pero este patrón es más largo que la sublista que representa, lo que hace que las cosas sean un poco más complejas. Los cinco signos de interrogación al final pueden considerarse no parte del patrón, por lo que tengo: "C {0NNNNNN} {11N ????} 10" como mi patrón, con el número que se repetirá almacenado en el "N "s. Eso es 2 bits demasiado largo.
Tendré que pedir prestados 2 bits y devolverlos de los 4 bits no utilizados en este patrón. Al leer, al encontrar "C {0NNNNNN} {11N00AB} 10", muestra 2 instancias del número en las "N", sobrescribe el "10" al final con los bits A y B, y rebobina el puntero de lectura en 2 bits Las lecturas destructivas están bien para este algoritmo, ya que cada lista compacta se recorre solo una vez.
Al escribir una sublista de 2 repeticiones de un solo número, escriba "C {0NNNNNN} 11N00" y establezca el contador de bits prestados en 2. En cada escritura donde el contador de bits prestados no es cero, se disminuye para cada bit escrito y "10" se escribe cuando el contador llega a cero. Por lo tanto, los siguientes 2 bits escritos irán a las ranuras A y B, y luego el "10" se dejará caer al final.
Con 3 valores de encabezado de sublista representados por "00", "01" y "1", puedo asignar "1" al tipo de sublista más popular. Necesitaré una tabla pequeña para asignar los valores de encabezado de sublista a los tipos de sublista, y necesitaré un contador de ocurrencias para cada tipo de sublista para que sepa cuál es la mejor asignación de encabezado de sublista.
La peor representación mínima de una lista compacta totalmente poblada ocurre cuando todos los tipos de sublista son igualmente populares. En ese caso, guardo 1 bit por cada 3 encabezados de sublista, por lo que el tamaño de la lista es 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083.3 bits. Redondeando a un límite de palabra de 32 bits, eso es 8302112 bits o 1037764 bytes.
1M menos los 2k para el estado TCP / IP y los buffers son 1022 * 1024 = 1046528 bytes, dejándome 8764 bytes para jugar.
Pero, ¿qué pasa con el proceso de cambiar la asignación de encabezado de sublista? En el mapa de memoria a continuación, "Z" es una sobrecarga aleatoria, "=" es espacio libre, "X" es la lista compacta.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Comience a leer en la "X" más a la izquierda y comience a escribir en la "=" más a la izquierda y trabaje a la derecha. Cuando termine, la lista compacta será un poco más corta y estará en el extremo incorrecto de la memoria:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
Entonces tendré que desviarlo a la derecha:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
En el proceso de cambio de asignación de encabezado, hasta 1/3 de los encabezados de sublista cambiarán de 1 bit a 2 bit. En el peor de los casos, todos estarán al principio de la lista, por lo que necesitaré al menos 781250/3 bits de almacenamiento gratuito antes de comenzar, lo que me lleva de vuelta a los requisitos de memoria de la versión anterior de la lista compacta: (
Para evitar eso, dividiré las sublistas 781250 en 10 grupos de sublistas de 78125 cada una. Cada grupo tiene su propia asignación de encabezado de sublista independiente. Usando las letras A a J para los grupos:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
Cada grupo de sublista se reduce o permanece igual durante un cambio de asignación de encabezado de sublista:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
El peor caso de expansión temporal de un grupo de sublista durante un cambio de mapeo es 78125/3 = 26042 bits, por debajo de 4k. Si permito 4k más los 1037764 bytes para una lista compacta completamente poblada, eso me deja 8764 - 4096 = 4668 bytes para las "Z" en el mapa de memoria.
Eso debería ser suficiente para las 10 tablas de mapeo de encabezado de sublista, 30 recuentos de ocurrencia de encabezado de sublista y los otros pocos contadores, punteros y pequeños buffers que necesitaré, y espacio que he usado sin darme cuenta, como espacio de pila para direcciones de retorno de llamadas de función y variables locales
Parte 3, ¿cuánto tiempo llevaría correr?
Con una lista compacta vacía, el encabezado de la lista de 1 bit se usará para una sublista vacía, y el tamaño inicial de la lista será de 781250 bits. En el peor de los casos, la lista crece 8 bits por cada número agregado, por lo que se necesitan 32 + 8 = 40 bits de espacio libre para cada uno de los números de 32 bits que se colocarán en la parte superior del búfer de la lista y luego se ordenarán y fusionarán. En el peor de los casos, cambiar la asignación de encabezado de sublista da como resultado un uso de espacio de 2 * 781250 + 7 * entradas - 781250/3 bits.
Con una política de cambiar el mapeo del encabezado de la sublista después de cada quinta fusión, una vez que haya al menos 800000 números en la lista, una ejecución de caso más desfavorable implicaría un total de aproximadamente 30 millones de actividades de lectura y escritura de listas compactas.
Fuente:
http://nick.cleaton.net/ramsortsol.html