Eine Lösung ist nur aufgrund des Unterschieds zwischen 1 Megabyte und 1 Million Bytes möglich. Es gibt ungefähr 2 Möglichkeiten 8093729.5 verschiedene Möglichkeiten, 1 Million 8-stellige Zahlen mit erlaubten Duplikaten auszuwählen und unwichtig zu bestellen, sodass eine Maschine mit nur 1 Million Bytes RAM nicht über genügend Zustände verfügt, um alle Möglichkeiten darzustellen. 1M (weniger 2k für TCP / IP) ist jedoch 1022 * 1024 * 8 = 8372224 Bit, sodass eine Lösung möglich ist.
Teil 1, erste Lösung
Dieser Ansatz benötigt etwas mehr als 1 Million, ich werde ihn verfeinern, um später in 1 Million zu passen.
Ich werde eine kompakte sortierte Liste von Zahlen im Bereich von 0 bis 99999999 als Folge von Unterlisten von 7-Bit-Zahlen speichern. Die erste Unterliste enthält Nummern von 0 bis 127, die zweite Unterliste enthält Nummern von 128 bis 255 usw. 100000000/128 ist genau 781250, daher werden 781250 solche Unterlisten benötigt.
Jede Unterliste besteht aus einem 2-Bit-Unterlisten-Header, gefolgt von einem Unterlisten-Body. Der Unterlistenkörper nimmt 7 Bits pro Unterlisteneintrag auf. Die Unterlisten sind alle miteinander verkettet, und das Format ermöglicht es zu erkennen, wo eine Unterliste endet und die nächste beginnt. Der Gesamtspeicher, der für eine vollständig aufgefüllte Liste erforderlich ist, beträgt 2 * 781250 + 7 * 1000000 = 8562500 Bit, was ungefähr 1,021 MByte entspricht.
Die 4 möglichen Unterlisten-Header-Werte sind:
00 Leere Unterliste, nichts folgt.
01 Singleton, es gibt nur einen Eintrag in der Unterliste und die nächsten 7 Bits enthalten ihn.
10 Die Unterliste enthält mindestens 2 verschiedene Nummern. Die Einträge werden in nicht absteigender Reihenfolge gespeichert, mit der Ausnahme, dass der letzte Eintrag kleiner oder gleich dem ersten ist. Dadurch kann das Ende der Unterliste identifiziert werden. Zum Beispiel würden die Zahlen 2,4,6 als (4,6,2) gespeichert. Die Zahlen 2,2,3,4,4 würden als (2,3,4,4,2) gespeichert.
11 Die Unterliste enthält zwei oder mehr Wiederholungen einer einzelnen Zahl. Die nächsten 7 Bits geben die Nummer an. Dann kommen null oder mehr 7-Bit-Einträge mit dem Wert 1, gefolgt von einem 7-Bit-Eintrag mit dem Wert 0. Die Länge des Unterlistenkörpers bestimmt die Anzahl der Wiederholungen. Zum Beispiel würden die Zahlen 12,12 als (12,0) gespeichert, die Zahlen 12,12,12 würden als (12,1,0) gespeichert, 12,12,12,12 wären (12,1) , 1,0) und so weiter.
Ich beginne mit einer leeren Liste, lese eine Reihe von Zahlen ein und speichere sie als 32-Bit-Ganzzahlen, sortiere die neuen Zahlen an Ort und Stelle (wahrscheinlich mit Heapsort) und füge sie dann zu einer neuen kompakten sortierten Liste zusammen. Wiederholen Sie diesen Vorgang, bis keine Zahlen mehr zu lesen sind, und gehen Sie dann die kompakte Liste erneut durch, um die Ausgabe zu generieren.
Die folgende Zeile zeigt den Speicher unmittelbar vor dem Start des Listenzusammenführungsvorgangs. Die "O" sind der Bereich, der die sortierten 32-Bit-Ganzzahlen enthält. Die "X" sind die Region, in der sich die alte Kompaktliste befindet. Die "=" - Zeichen sind der Erweiterungsraum für die kompakte Liste, 7 Bits für jede Ganzzahl in den "O". Die "Z" sind andere zufällige Gemeinkosten.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
Die Zusammenführungsroutine beginnt am linken "O" und am linken "X" zu lesen und beginnt am linken "=" zu schreiben. Der Schreibzeiger fängt den Lesezeiger der kompakten Liste erst ab, wenn alle neuen Ganzzahlen zusammengeführt wurden, da beide Zeiger 2 Bit für jede Unterliste und 7 Bit für jeden Eintrag in der alten kompakten Liste vorrücken und genügend zusätzlicher Platz für die vorhanden ist 7-Bit-Einträge für die neuen Nummern.
Teil 2, stopft es in 1M
Um die obige Lösung in 1M zusammenzufassen, muss ich das kompakte Listenformat etwas kompakter gestalten. Ich werde einen der Unterlistentypen loswerden, so dass es nur 3 verschiedene mögliche Unterlisten-Header-Werte gibt. Dann kann ich "00", "01" und "1" als Unterlisten-Header-Werte verwenden und einige Bits speichern. Die Unterlistentypen sind:
Eine leere Unterliste, nichts folgt.
B Singleton, es gibt nur einen Eintrag in der Unterliste und die nächsten 7 Bits enthalten ihn.
C Die Unterliste enthält mindestens 2 verschiedene Nummern. Die Einträge werden in nicht absteigender Reihenfolge gespeichert, mit der Ausnahme, dass der letzte Eintrag kleiner oder gleich dem ersten ist. Dadurch kann das Ende der Unterliste identifiziert werden. Zum Beispiel würden die Zahlen 2,4,6 als (4,6,2) gespeichert. Die Zahlen 2,2,3,4,4 würden als (2,3,4,4,2) gespeichert.
D Die Unterliste besteht aus 2 oder mehr Wiederholungen einer einzelnen Zahl.
Meine 3 Unterlisten-Header-Werte sind "A", "B" und "C", daher brauche ich eine Möglichkeit, Unterlisten vom Typ D darzustellen.
Angenommen, ich habe den C-Typ-Unterlisten-Header, gefolgt von 3 Einträgen, z. B. "C [17] [101] [58]". Dies kann nicht Teil einer gültigen Unterliste vom Typ C sein, wie oben beschrieben, da der dritte Eintrag kleiner als der zweite, aber größer als der erste ist. Ich kann diese Art von Konstrukt verwenden, um eine Unterliste vom Typ D darzustellen. In Bit-Begriffen ist überall, wo ich "C {00 ?????} {1 ??????} {01 ?????}" habe, eine unmögliche Unterliste vom Typ C. Ich werde dies verwenden, um eine Unterliste darzustellen, die aus 3 oder mehr Wiederholungen einer einzelnen Zahl besteht. Die ersten beiden 7-Bit-Wörter codieren die Zahl (die "N" -Bits unten) und werden von null oder mehr {0100001} Wörtern gefolgt von einem {0100000} Wort gefolgt.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
Das lässt nur Listen übrig, die genau 2 Wiederholungen einer einzelnen Zahl enthalten. Ich werde diejenigen mit einem anderen unmöglichen C-Typ-Unterlistenmuster darstellen: "C {0 ??????} {11 ?????} {10 ?????}". Es gibt viel Platz für die 7 Bits der Zahl in den ersten 2 Wörtern, aber dieses Muster ist länger als die Unterliste, die es darstellt, was die Dinge etwas komplexer macht. Die fünf Fragezeichen am Ende können als nicht Teil des Musters betrachtet werden, daher habe ich: "C {0NNNNNN} {11N ????} 10" als mein Muster, wobei die zu wiederholende Nummer im "N" gespeichert ist "s. Das sind 2 Bits zu lang.
Ich muss 2 Bits ausleihen und sie von den 4 nicht verwendeten Bits in diesem Muster zurückzahlen. Wenn Sie beim Lesen auf "C {0NNNNNN} {11N00AB} 10" stoßen, geben Sie 2 Instanzen der Zahl in den "N" aus, überschreiben Sie die "10" am Ende mit den Bits A und B und spulen Sie den Lesezeiger um 2 zurück Bits. Destruktive Lesevorgänge sind für diesen Algorithmus in Ordnung, da jede kompakte Liste nur einmal durchlaufen wird.
Wenn Sie eine Unterliste mit 2 Wiederholungen einer einzelnen Zahl schreiben, schreiben Sie "C {0NNNNNN} 11N00" und setzen Sie den Zähler für ausgeliehene Bits auf 2. Bei jedem Schreibvorgang, bei dem der Zähler für ausgeliehene Bits ungleich Null ist, wird er für jedes geschriebene Bit und dekrementiert "10" wird geschrieben, wenn der Zähler Null erreicht. Die nächsten 2 geschriebenen Bits werden also in die Steckplätze A und B geschrieben, und dann wird die "10" am Ende abgelegt.
Mit 3 Unterlisten-Header-Werten, die durch "00", "01" und "1" dargestellt werden, kann ich dem beliebtesten Unterlistentyp "1" zuweisen. Ich benötige eine kleine Tabelle, um die Werte der Unterlisten-Header den Unterlistentypen zuzuordnen, und ich benötige einen Vorkommenszähler für jeden Unterlistentyp, damit ich weiß, was die beste Zuordnung der Unterlisten-Header ist.
Die minimale Darstellung einer vollständig ausgefüllten kompakten Liste im schlimmsten Fall tritt auf, wenn alle Unterlistentypen gleich beliebt sind. In diesem Fall speichere ich 1 Bit für jeweils 3 Unterlisten-Header, sodass die Listengröße 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083.3 Bit beträgt. Aufrunden auf eine 32-Bit-Wortgrenze, dh 8302112 Bit oder 1037764 Byte.
1M minus 2k für TCP / IP-Status und Puffer beträgt 1022 * 1024 = 1046528 Bytes, sodass ich 8764 Bytes zum Spielen habe.
Aber was ist mit dem Prozess der Änderung der Zuordnung der Unterlisten-Header? In der Speicherkarte unten ist "Z" zufälliger Overhead, "=" ist freier Speicherplatz, "X" ist die kompakte Liste.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Beginnen Sie mit dem Lesen ganz links "X" und beginnen Sie ganz links mit dem Schreiben "=" und arbeiten Sie rechts. Wenn dies erledigt ist, ist die kompakte Liste etwas kürzer und befindet sich am falschen Ende des Speichers:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
Dann muss ich es nach rechts rangieren:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Beim Ändern der Headerzuordnung ändern sich bis zu 1/3 der Header der Unterliste von 1-Bit auf 2-Bit. Im schlimmsten Fall stehen diese alle an der Spitze der Liste, sodass ich vor dem Start mindestens 781250/3 Bit freien Speicherplatz benötige, was mich zu den Speicheranforderungen der vorherigen Version der Kompaktliste zurückführt: (
Um dies zu umgehen, werde ich die 781250-Unterlisten in 10 Unterlistengruppen mit jeweils 78125-Unterlisten aufteilen. Jede Gruppe verfügt über eine eigene unabhängige Unterlisten-Headerzuordnung. Verwenden Sie die Buchstaben A bis J für die Gruppen:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
Jede Unterlistengruppe wird während einer Änderung der Zuordnung der Unterlisten-Header verkleinert oder bleibt gleich:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
Die temporäre Erweiterung einer Unterlistengruppe im schlimmsten Fall während einer Zuordnungsänderung beträgt 78125/3 = 26042 Bit unter 4 KB. Wenn ich 4 KB plus 1037764 Bytes für eine vollständig aufgefüllte kompakte Liste zulasse, bleiben mir 8764 - 4096 = 4668 Bytes für die "Z" in der Speicherzuordnung.
Das sollte für die 10 Unterlisten-Header-Zuordnungstabellen, 30 Unterlisten-Header-Auftrittszählungen und die anderen wenigen Zähler, Zeiger und kleinen Puffer, die ich benötige, und den Speicherplatz, den ich ohne es zu bemerken verwendet habe, wie Stapelspeicher für Rückrufadressen von Funktionsaufrufen und lokale Variablen.
Teil 3, wie lange würde es dauern, um zu laufen?
Bei einer leeren kompakten Liste wird der 1-Bit-Listenkopf für eine leere Unterliste verwendet, und die Startgröße der Liste beträgt 781250 Bit. Im schlimmsten Fall wächst die Liste um 8 Bits für jede hinzugefügte Nummer, sodass 32 + 8 = 40 Bits freier Speicherplatz für jede der 32-Bit-Nummern benötigt werden, um oben im Listenpuffer platziert und dann sortiert und zusammengeführt zu werden. Im schlimmsten Fall führt das Ändern der Header-Zuordnung der Unterliste zu einer Speicherplatznutzung von 2 * 781250 + 7 * Einträgen - 781250/3 Bit.
Mit der Richtlinie, die Zuordnung von Unterlisten-Headern nach jeder fünften Zusammenführung zu ändern, sobald mindestens 800000 Nummern in der Liste enthalten sind, würde ein Worst-Case-Lauf insgesamt etwa 30 Millionen Lese- und Schreibaktivitäten für kompakte Listen umfassen.
Quelle:
http://nick.cleaton.net/ramsortsol.html