Une solution n'est possible qu'en raison de la différence entre 1 mégaoctet et 1 million d'octets. Il y a environ 2 à la puissance 8093729.5 différentes façons de choisir 1 million de numéros à 8 chiffres avec des doublons autorisés et de commander sans importance, donc une machine avec seulement 1 million d'octets de RAM n'a pas assez d'états pour représenter toutes les possibilités. Mais 1M (moins 2k pour TCP / IP) est 1022 * 1024 * 8 = 8372224 bits, donc une solution est possible.
Partie 1, solution initiale
Cette approche a besoin d'un peu plus de 1M, je vais l'affiner pour l'adapter à 1M plus tard.
Je vais stocker une liste triée compacte de nombres compris entre 0 et 99999999 sous la forme d'une séquence de sous-listes de nombres à 7 bits. La première sous-liste contient des nombres de 0 à 127, la deuxième sous-liste contient des nombres de 128 à 255, etc. 100000000/128 est exactement 781250, donc 781250 de telles sous-listes seront nécessaires.
Chaque sous-liste se compose d'un en-tête de sous-liste de 2 bits suivi d'un corps de sous-liste. Le corps de sous-liste occupe 7 bits par entrée de sous-liste. Les sous-listes sont toutes concaténées ensemble, et le format permet de dire où se termine une sous-liste et où commence la suivante. La mémoire totale requise pour une liste entièrement remplie est de 2 * 781250 + 7 * 1000000 = 8562500 bits, soit environ 1,021 M-octets.
Les 4 valeurs d'en-tête de sous-liste possibles sont:
00 Sous-liste vide, rien ne suit.
01 Singleton, il n'y a qu'une seule entrée dans la sous-liste et les 7 bits suivants la contiennent.
10 La sous-liste contient au moins 2 nombres distincts. Les entrées sont stockées dans un ordre non décroissant, sauf que la dernière entrée est inférieure ou égale à la première. Cela permet d'identifier la fin de la sous-liste. Par exemple, les nombres 2,4,6 seraient stockés sous la forme (4,6,2). Les nombres 2,2,3,4,4 seraient stockés sous la forme (2,3,4,4,2).
11 La sous-liste contient au moins 2 répétitions d'un même numéro. Les 7 bits suivants donnent le nombre. Viennent ensuite zéro ou plusieurs entrées de 7 bits avec la valeur 1, suivies d'une entrée de 7 bits avec la valeur 0. La longueur du corps de sous-liste dicte le nombre de répétitions. Par exemple, les nombres 12,12 seraient stockés comme (12,0), les nombres 12,12,12 seraient stockés comme (12,1,0), 12,12,12,12 seraient (12,1 , 1,0) et ainsi de suite.
Je commence par une liste vide, je lis un tas de nombres et je les stocke sous forme d'entiers 32 bits, je trie les nouveaux numéros en place (à l'aide de heapsort, probablement), puis je les fusionne dans une nouvelle liste triée compacte. Répétez jusqu'à ce qu'il n'y ait plus de chiffres à lire, puis parcourez à nouveau la liste compacte pour générer la sortie.
La ligne ci-dessous représente la mémoire juste avant le début de l'opération de fusion de liste. Les "O" sont la région qui contient les entiers triés de 32 bits. Les «X» sont la région qui contient l'ancienne liste compacte. Les signes "=" sont l'espace d'extension pour la liste compacte, 7 bits pour chaque entier dans les "O". Les «Z» sont d'autres frais généraux aléatoires.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
La routine de fusion commence à lire au "O" le plus à gauche et au "X" le plus à gauche, et commence à écrire au "=" le plus à gauche. Le pointeur d'écriture n'attrape pas le pointeur de lecture de liste compacte jusqu'à ce que tous les nouveaux entiers soient fusionnés, car les deux pointeurs avancent 2 bits pour chaque sous-liste et 7 bits pour chaque entrée de l'ancienne liste compacte, et il y a suffisamment d'espace supplémentaire pour le Entrées 7 bits pour les nouveaux numéros.
Partie 2, entasser en 1M
Pour compresser la solution ci-dessus en 1 Mo, je dois rendre le format de liste compact un peu plus compact. Je vais me débarrasser de l'un des types de sous-liste, de sorte qu'il n'y aura que 3 différentes valeurs d'en-tête de sous-liste possibles. Ensuite, je peux utiliser "00", "01" et "1" comme valeurs d'en-tête de sous-liste et enregistrer quelques bits. Les types de sous-listes sont:
Une sous-liste vide, rien ne suit.
B Singleton, il n'y a qu'une seule entrée dans la sous-liste et les 7 bits suivants la contiennent.
C La sous-liste contient au moins 2 nombres distincts. Les entrées sont stockées dans un ordre non décroissant, sauf que la dernière entrée est inférieure ou égale à la première. Cela permet d'identifier la fin de la sous-liste. Par exemple, les nombres 2,4,6 seraient stockés sous la forme (4,6,2). Les nombres 2,2,3,4,4 seraient stockés sous la forme (2,3,4,4,2).
D La sous-liste consiste en 2 répétitions ou plus d'un même numéro.
Mes 3 valeurs d'en-tête de sous-liste seront "A", "B" et "C", j'ai donc besoin d'un moyen de représenter les sous-listes de type D.
Supposons que j'ai l'en-tête de sous-liste de type C suivi de 3 entrées, telles que "C [17] [101] [58]". Cela ne peut pas faire partie d'une sous-liste de type C valide comme décrit ci-dessus, car la troisième entrée est inférieure à la seconde mais supérieure à la première. Je peux utiliser ce type de construction pour représenter une sous-liste de type D. En termes de bits, partout où j'ai "C {00 ?????} {1 ??????} {01 ?????}" est une sous-liste de type C impossible. Je vais l'utiliser pour représenter une sous-liste composée de 3 répétitions ou plus d'un même numéro. Les deux premiers mots de 7 bits codent le nombre (les "N" bits ci-dessous) et sont suivis de zéro ou plusieurs mots {0100001} suivis d'un mot {0100000}.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
Cela laisse juste des listes qui contiennent exactement 2 répétitions d'un même numéro. Je vais représenter ceux avec un autre motif de sous-liste de type C impossible: "C {0 ??????} {11 ?????} {10 ?????}". Il y a beaucoup de place pour les 7 bits du nombre dans les 2 premiers mots, mais ce modèle est plus long que la sous-liste qu'il représente, ce qui rend les choses un peu plus complexes. Les cinq points d'interrogation à la fin peuvent être considérés comme ne faisant pas partie du modèle, j'ai donc: "C {0NNNNNN} {11N ????} 10" comme modèle, avec le nombre à répéter stocké dans le "N "s. C'est 2 bits de trop.
Je vais devoir emprunter 2 bits et les rembourser sur les 4 bits inutilisés de ce modèle. Lors de la lecture, en rencontrant "C {0NNNNNN} {11N00AB} 10", sortez 2 instances du nombre dans les "N", écrasez le "10" à la fin avec les bits A et B et rembobinez le pointeur de lecture de 2 morceaux. Les lectures destructives sont acceptables pour cet algorithme, car chaque liste compacte n'est parcourue qu'une seule fois.
Lors de l'écriture d'une sous-liste de 2 répétitions d'un même nombre, écrivez "C {0NNNNNN} 11N00" et réglez le compteur de bits empruntés sur 2. A chaque écriture où le compteur de bits empruntés est non nul, il est décrémenté pour chaque bit écrit et "10" est écrit lorsque le compteur atteint zéro. Ainsi, les 2 bits suivants écrits iront dans les emplacements A et B, puis le "10" sera déposé à la fin.
Avec 3 valeurs d'en-tête de sous-liste représentées par "00", "01" et "1", je peux affecter "1" au type de sous-liste le plus populaire. J'aurai besoin d'une petite table pour mapper les valeurs d'en-tête de sous-liste aux types de sous-liste, et j'aurai besoin d'un compteur d'occurrences pour chaque type de sous-liste afin de savoir quel est le meilleur mappage d'en-tête de sous-liste.
La pire représentation minimale d'une liste compacte entièrement remplie se produit lorsque tous les types de sous-listes sont également populaires. Dans ce cas, j'enregistre 1 bit pour 3 en-têtes de sous-liste, la taille de la liste est donc de 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083,3 bits. Arrondi à une limite de mots de 32 bits, soit 8302112 bits ou 1037764 octets.
1M moins le 2k pour l'état TCP / IP et les tampons est de 1022 * 1024 = 1046528 octets, ce qui me laisse 8764 octets pour jouer.
Mais qu'en est-il du processus de modification du mappage d'en-tête de sous-liste? Dans la carte mémoire ci-dessous, "Z" est une surcharge aléatoire, "=" est un espace libre, "X" est la liste compacte.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Commencez à lire au "X" le plus à gauche et commencez à écrire au "=" le plus à gauche et travaillez à droite. Une fois terminé, la liste compacte sera un peu plus courte et se trouvera à la mauvaise extrémité de la mémoire:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
Alors je vais devoir le shunter vers la droite:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Dans le processus de changement de mappage d'en-tête, jusqu'à 1/3 des en-têtes de sous-liste passeront de 1 bit à 2 bits. Dans le pire des cas, ceux-ci seront tous en tête de liste, j'ai donc besoin d'au moins 781250/3 bits de stockage gratuit avant de commencer, ce qui me ramène aux besoins en mémoire de la version précédente de la liste compacte: (
Pour contourner cela, je vais diviser les sous-listes 781250 en 10 groupes de sous-listes de 78125 sous-listes chacun. Chaque groupe a son propre mappage d'en-tête de sous-liste indépendant. En utilisant les lettres A à J pour les groupes:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
Chaque groupe de sous-listes diminue ou reste le même lors d'un changement de mappage d'en-tête de sous-liste:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
La pire expansion temporaire d'un groupe de sous-listes lors d'un changement de mappage est 78125/3 = 26042 bits, sous 4k. Si j'autorise 4k plus les 1037764 octets pour une liste compacte entièrement remplie, cela me laisse 8764 - 4096 = 4668 octets pour les "Z" dans la carte mémoire.
Cela devrait être suffisant pour les 10 tables de mappage d'en-tête de sous-liste, 30 comptes d'occurrence d'en-tête de sous-liste et les quelques autres compteurs, pointeurs et petits tampons dont j'ai besoin, et l'espace que j'ai utilisé sans le remarquer, comme l'espace de pile pour les adresses de retour d'appel de fonction et variables locales.
Partie 3, combien de temps faudrait-il pour fonctionner?
Avec une liste compacte vide, l'en-tête de liste 1 bit sera utilisé pour une sous-liste vide, et la taille de départ de la liste sera de 781250 bits. Dans le pire des cas, la liste augmente de 8 bits pour chaque numéro ajouté, donc 32 + 8 = 40 bits d'espace libre sont nécessaires pour chacun des numéros de 32 bits à placer en haut du tampon de liste, puis triés et fusionnés. Dans le pire des cas, la modification du mappage d'en-tête de sous-liste entraîne une utilisation de l'espace de 2 * 781250 + 7 * entrées - 781250/3 bits.
Avec une politique de modification du mappage d'en-tête de sous-liste après chaque cinquième fusion une fois qu'il y a au moins 800000 numéros dans la liste, une exécution dans le pire des cas impliquerait un total d'environ 30 millions d'activités de lecture et d'écriture de liste compacte.
La source:
http://nick.cleaton.net/ramsortsol.html