Una soluzione è possibile solo a causa della differenza tra 1 megabyte e 1 milione di byte. Ci sono circa 2 alla potenza 8093729.5 diversi modi per scegliere 1 milione di numeri a 8 cifre con duplicati consentiti e ordini non importanti, quindi una macchina con solo 1 milione di byte di RAM non ha abbastanza stati per rappresentare tutte le possibilità. Ma 1M (meno 2k per TCP / IP) è 1022 * 1024 * 8 = 8372224 bit, quindi è possibile una soluzione.
Parte 1, soluzione iniziale
Questo approccio richiede poco più di 1 milione, lo perfezionerò per adattarlo a 1 milione più tardi.
Memorizzerò un elenco compatto di numeri compreso tra 0 e 99999999 come una sequenza di elenchi di numeri a 7 bit. Il primo elenco secondario contiene numeri da 0 a 127, il secondo elenco secondario contiene numeri da 128 a 255, ecc. 100000000/128 è esattamente 781250, quindi saranno necessari 781250 tali elenchi.
Ogni elenco secondario è costituito da un'intestazione dell'elenco secondario a 2 bit seguita da un corpo dell'elenco secondario. Il corpo della lista secondaria occupa 7 bit per voce della lista secondaria. Le liste secondarie sono tutte concatenate insieme e il formato consente di dire dove finisce una lista secondaria e inizia la successiva. La memoria totale richiesta per un elenco completamente popolato è 2 * 781250 + 7 * 1000000 = 8562500 bit, ovvero circa 1,021 M-byte.
I 4 possibili valori dell'intestazione dell'elenco secondario sono:
00 Elenco vuoto, non segue nulla.
01 Singleton, c'è solo una voce nell'elenco secondario e i successivi 7 bit lo tengono.
10 L'elenco secondario contiene almeno 2 numeri distinti. Le voci sono memorizzate in ordine non decrescente, tranne per il fatto che l'ultima voce è inferiore o uguale alla prima. Ciò consente di identificare la fine dell'elenco secondario. Ad esempio, i numeri 2,4,6 verrebbero memorizzati come (4,6,2). I numeri 2,2,3,4,4 verrebbero memorizzati come (2,3,4,4,2).
11 L'elenco secondario contiene 2 o più ripetizioni di un singolo numero. I successivi 7 bit danno il numero. Quindi arrivano zero o più voci a 7 bit con il valore 1, seguite da una voce a 7 bit con il valore 0. La lunghezza del corpo dell'elenco secondario determina il numero di ripetizioni. Ad esempio, i numeri 12,12 verrebbero memorizzati come (12,0), i numeri 12,12,12 verrebbero memorizzati come (12,1,0), 12,12,12,12 sarebbero (12,1 , 1,0) e così via.
Comincio con un elenco vuoto, leggo un mucchio di numeri e li memorizzo come numeri interi a 32 bit, ordina i nuovi numeri sul posto (usando heapsort, probabilmente) e poi li unisco in un nuovo elenco ordinato compatto. Ripetere fino a quando non ci sono più numeri da leggere, quindi percorrere di nuovo l'elenco compatto per generare l'output.
La riga seguente rappresenta la memoria poco prima dell'inizio dell'operazione di unione elenco. Le "O" sono la regione che contiene gli interi a 32 bit ordinati. Le "X" sono la regione che contiene il vecchio elenco compatto. I segni "=" sono lo spazio di espansione per l'elenco compatto, 7 bit per ogni numero intero nelle "O". Le "Z" sono altre spese generali casuali.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
La routine di unione inizia a leggere all'estrema sinistra "O" e all'estrema sinistra "X", e inizia a scrivere all'estrema sinistra "=". Il puntatore di scrittura non rileva il puntatore di lettura dell'elenco compatto fino a quando tutti i nuovi numeri interi non vengono uniti, poiché entrambi i puntatori avanzano di 2 bit per ciascun elenco secondario e di 7 bit per ciascuna voce dell'elenco compatto precedente e non vi è spazio aggiuntivo sufficiente per Voci a 7 bit per i nuovi numeri.
Parte 2, stipandolo in 1M
Per comprimere la soluzione sopra in 1M, devo rendere il formato dell'elenco compatto un po 'più compatto. Mi libererò di uno dei tipi di elenco secondario, in modo che ci siano solo 3 diversi valori di intestazione dell'elenco secondario possibili. Quindi posso usare "00", "01" e "1" come valori dell'intestazione dell'elenco secondario e salvare alcuni bit. I tipi di elenco secondario sono:
Un elenco secondario vuoto, non segue nulla.
B Singleton, c'è solo una voce nell'elenco secondario e i successivi 7 bit lo tengono.
C L'elenco secondario contiene almeno 2 numeri distinti. Le voci sono memorizzate in ordine non decrescente, tranne per il fatto che l'ultima voce è inferiore o uguale alla prima. Ciò consente di identificare la fine dell'elenco secondario. Ad esempio, i numeri 2,4,6 verrebbero memorizzati come (4,6,2). I numeri 2,2,3,4,4 verrebbero memorizzati come (2,3,4,4,2).
D La lista secondaria è composta da 2 o più ripetizioni di un singolo numero.
I miei 3 valori di intestazione dell'elenco secondario saranno "A", "B" e "C", quindi ho bisogno di un modo per rappresentare gli elenchi secondari di tipo D.
Supponiamo di avere l'intestazione dell'elenco secondario di tipo C seguita da 3 voci, ad esempio "C [17] [101] [58]". Questo non può far parte di un elenco secondario di tipo C valido come descritto sopra, poiché la terza voce è inferiore alla seconda ma più della prima. Posso usare questo tipo di costrutto per rappresentare un elenco secondario di tipo D. In parole povere, ovunque io abbia "C {00 ?????} {1 ??????} {01 ?????}" è un elenco secondario di tipo C impossibile. Userò questo per rappresentare un elenco secondario composto da 3 o più ripetizioni di un singolo numero. Le prime due parole a 7 bit codificano il numero (i bit "N" in basso) e sono seguite da zero o più parole {0100001} seguite da una parola {0100000}.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
Ciò lascia solo elenchi che contengono esattamente 2 ripetizioni di un singolo numero. Rappresenterò quelli con un altro schema di sublist di tipo C impossibile: "C {0 ??????} {11 ?????} {10 ?????}". C'è un sacco di spazio per i 7 bit del numero nelle prime 2 parole, ma questo schema è più lungo dell'elenco secondario che rappresenta, il che rende le cose un po 'più complesse. I cinque punti interrogativi alla fine possono essere considerati non parte del modello, quindi ho: "C {0NNNNNN} {11N ????} 10" come modello, con il numero da ripetere memorizzato nella "N "S. Sono 2 bit troppo lunghi.
Dovrò prendere in prestito 2 bit e ripagarli dai 4 bit non utilizzati in questo modello. Durante la lettura, quando si incontra "C {0NNNNNN} {11N00AB} 10", emettere 2 istanze del numero nelle "N", sovrascrivere "10" alla fine con i bit A e B e riavvolgere il puntatore di lettura di 2 bit. Letture distruttive sono ok per questo algoritmo, poiché ogni elenco compatto viene camminato una sola volta.
Quando si scrive un elenco secondario di 2 ripetizioni di un singolo numero, scrivere "C {0NNNNNN} 11N00" e impostare il contatore dei bit presi in prestito su 2. Ad ogni scrittura in cui il contatore dei bit presi in prestito è diverso da zero, viene ridotto per ogni bit scritto e "10" viene scritto quando il contatore colpisce zero. Quindi i successivi 2 bit scritti andranno negli slot A e B, quindi i "10" verranno rilasciati alla fine.
Con 3 valori di intestazione dell'elenco secondario rappresentati da "00", "01" e "1", posso assegnare "1" al tipo di elenco secondario più popolare. Avrò bisogno di una piccola tabella per mappare i valori dell'intestazione dell'elenco secondario ai tipi di elenco secondario e avrò bisogno di un contatore di occorrenze per ciascun tipo di elenco secondario in modo da sapere qual è il migliore mapping dell'intestazione dell'elenco secondario.
La rappresentazione minima del caso peggiore di un elenco compatto completamente popolato si verifica quando tutti i tipi di elenco secondario sono ugualmente popolari. In tal caso, salvo 1 bit per ogni 3 intestazioni dell'elenco secondario, quindi la dimensione dell'elenco è 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083,3 bit. Arrotondando per eccesso a un limite di parola a 32 bit, sono 8302112 bit o 1037764 byte.
1M meno il 2k per lo stato e i buffer TCP / IP è 1022 * 1024 = 1046528 byte, lasciandomi 8764 byte con cui giocare.
Ma per quanto riguarda il processo di modifica del mapping dell'intestazione dell'elenco secondario? Nella mappa di memoria in basso, "Z" è un overhead casuale, "=" è spazio libero, "X" è l'elenco compatto.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Inizia a leggere all'estrema sinistra "X" e inizia a scrivere all'estrema sinistra "=" e lavora a destra. Al termine, l'elenco compatto sarà un po 'più corto e si troverà nella parte sbagliata della memoria:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
Quindi dovrò spostarlo a destra:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Nel processo di modifica della mappatura delle intestazioni, fino a 1/3 delle intestazioni della sublist passerà da 1-bit a 2-bit. Nel peggiore dei casi questi saranno tutti in testa alla lista, quindi avrò bisogno di almeno 781250/3 bit di spazio di archiviazione gratuito prima di iniziare, il che mi riporta ai requisiti di memoria della versione precedente dell'elenco compatto: (
Per ovviare a questo, dividerò le 781250 liste secondarie in 10 gruppi di liste secondarie di 78125 liste secondarie ciascuna. Ogni gruppo ha il proprio mapping di intestazione dell'elenco secondario indipendente. Usando le lettere dalla A alla J per i gruppi:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
Ogni gruppo di elenchi secondari si restringe o rimane lo stesso durante una modifica del mapping dell'intestazione dell'elenco secondario:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
L'espansione temporanea del caso peggiore di un gruppo di elenchi secondari durante una modifica della mappatura è 78125/3 = 26042 bit, inferiore a 4k. Se consento 4k più i 1037764 byte per un elenco compatto completamente popolato, ciò mi lascia 8764 - 4096 = 4668 byte per le "Z" nella mappa di memoria.
Questo dovrebbe essere sufficiente per le 10 tabelle di mappatura delle intestazioni della sublist, 30 conteggi delle occorrenze delle intestazioni della sublist e gli altri pochi contatori, puntatori e piccoli buffer di cui avrò bisogno, e lo spazio che ho usato senza notare, come lo spazio dello stack per gli indirizzi di ritorno delle chiamate di funzione e variabili locali.
Parte 3, quanto tempo ci vorrebbe per funzionare?
Con un elenco compatto vuoto, l'intestazione dell'elenco a 1 bit verrà utilizzata per un elenco secondario vuoto e la dimensione iniziale dell'elenco sarà 781250 bit. Nel peggiore dei casi l'elenco aumenta di 8 bit per ogni numero aggiunto, quindi sono necessari 32 + 8 = 40 bit di spazio libero per ciascuno dei numeri a 32 bit da posizionare nella parte superiore del buffer dell'elenco e quindi ordinato e unito. Nel peggiore dei casi, la modifica della mappatura dell'intestazione dell'elenco secondario comporta un utilizzo dello spazio di 2 * 781250 + 7 * voci - 781250/3 bit.
Con una politica di modifica del mapping dell'intestazione dell'elenco secondario dopo ogni quinta unione una volta che ci sono almeno 800000 numeri nell'elenco, un caso peggiore comporterebbe un totale di circa 30 milioni di attività di lettura e scrittura dell'elenco compatto.
Fonte:
http://nick.cleaton.net/ramsortsol.html