Array in amortisierter konstanter Zeit initialisieren - wie heißt dieser Trick?


13

Es gibt diese Datenstruktur, die die Leistung des Array-Zugriffs mit der Notwendigkeit vergleicht, sie beim Löschen zu durchlaufen. Sie führen mit jedem Eintrag einen Generationszähler sowie einen globalen Generationszähler. Die "Lösch" -Operation erhöht den Erzeugungszähler. Bei jedem Zugriff vergleichen Sie lokale und globale Generationszähler. Wenn sie sich unterscheiden, wird der Wert als "sauber" behandelt.

Dies ist in dieser Antwort auf Stack Overflow kürzlich aufgetaucht , aber ich erinnere mich nicht, ob dieser Trick einen offiziellen Namen hat. Macht es?

Ein Anwendungsfall ist der Dijkstra-Algorithmus, wenn nur eine winzige Teilmenge der Knoten gelockert werden muss und dies wiederholt erfolgen muss.


2
Interessanter Trick, aber es hat einen ziemlichen Overhead. Also frage ich mich, welche Verwendungen haben das Löschen des Arrays als eine solche gemeinsame Operation, die der Preis zahlt? (Aufrichtige Frage!)
Joachim Sauer

@ JoachimSauer: Bearbeitet.
krlmlr

Klingt im Allgemeinen sowohl für die Speichernutzung als auch für die Zugriffskosten sehr teuer. Der Anwendungsfall für diese Technik muss sehr spezifisch sein.
Martin York

3
@Joachim: Es wird verwendet, um Puffer für das Rendering schnell und grob zu löschen. Sie haben nur ein "klares Bit" pro 64kb oder so ähnlich.
DeadMG

3
@ user946850 "amortisiert" bedeutet, dass Sie beweisen können, dass eine teure Operation im Gesamtbild selten genug vorkommt, dass sie nicht mehr beiträgt als z. B. O (1)

Antworten:


2

Der vorgenannte Ansatz erfordert, dass jede Zelle eine Zahl halten kann, die groß genug ist, um die Anzahl der Neuinitialisierungen des Arrays zu halten, was eine erhebliche Raumstrafe darstellt. Wenn ein Slot in der Lage ist, mindestens einen Wert zu speichern, der niemals legitim geschrieben werden kann, kann vermieden werden, dass eine andere (nicht konstante) Platzstrafe auf Kosten der Hinzufügung einer O(Wlg(N))Zeitstrafe auftritt, wobei Wdie Anzahl der zwischengeschriebenen unterschiedlichen Array-Slots angegeben wird Löschvorgänge und Nist die Größe des Arrays. Angenommen, man speichert Ganzzahlen von -2.147.483.647 bis 2.147.483.647 (aber niemals -2.147.483.648) und möchte, dass leere Array-Elemente als Null gelesen werden. Füllen Sie zunächst das Array mit -2.147.483.648 (nennen Sie diesen WertB). Geben Sie beim Lesen eines Array-Steckplatzes für die Anwendung den Wert BNull an. Vor dem Array Schlitz Schreiben I, zu prüfen , ob er gehalten , Bund wenn ja , und Igrößer als eins ist , eine Null speichern Schlitz I/4nach einem ähnlichen Kontroll für diesen Ort durchführt (und, wenn es gehalten B, I/16usw.).

Beginnen Sie zum Löschen des Arrays mit I0 oder 1, je nach Array-Basis (der beschriebene Algorithmus funktioniert auch für beide). Wiederholen Sie dann den folgenden Vorgang: Wenn item gleich Iist B, erhöhen Sie Iund dividieren Sie durch vier, wenn dies ein Vielfaches von vier ergibt (beenden Sie, wenn die Division einen Wert von 1 ergibt); Ist dies Inicht der Fall B, speichern Sie Bes und multiplizieren Sie es Imit vier ( Ibeginnt es bei Null, wird es mit vier multipliziert, Iwird es jedoch inkrementiert , da Punkt 0 leer sein wird).

Man beachte, dass man die Konstante "vier" oben durch andere Zahlen ersetzen könnte, wobei größere Werte im Allgemeinen weniger Arbeitsmarkierungen erfordern, aber kleinere Werte im Allgemeinen weniger Arbeitslöschung erfordern; Da markierte Array-Slots gelöscht werden müssen, ist ein Wert von drei oder vier mit ziemlicher Sicherheit optimal. da der Wert vier sicherlich nahezu optimal ist, besser als zwei oder acht ist und bequemer als jede andere Zahl, scheint es die vernünftigste Wahl zu sein.


Es ist ausreichend, einen Versionszähler zu haben, der in der Lage ist, genügend sequentielle Zurücksetzungen durchzuführen, bevor alle Zellen mit neuen Werten aktualisiert werden. In der Praxis kann ein Byte ausreichen oder in engeren Schleifen sogar weniger.
9000

@ 9000: Code, der sich auf ein solches Verhalten stützt, ist leicht zerbrechlich, zumal der einzige Grund für die Verwendung eines solchen "Pseudo-Clear" -Ansatzes (im Gegensatz zum einfachen Löschen des Arrays) die Menge der benötigten Elemente wäre Das Löschen war normalerweise klein und variabel - ein Paar von Bedingungen, die die Wahrscheinlichkeit erhöhen, dass ein Gegenstand verwendet wird, "gelöscht" und dann für eine willkürlich lange Zeit unberührt bleibt. Man könnte in Betracht ziehen, das Array zu scannen und alle alten Slots physisch zu
löschen,

1
... wenn der Wrap-Wert des Zählers konstant ist, wäre der durchschnittliche Arbeitsaufwand für jede Array-Löschoperation O (N), wobei N die Größe des Arrays ist. Nicht, dass so etwas in der Praxis nicht sinnvoll wäre, da eine um den Faktor 65.536 beschleunigte O (N) -Implementierung immer noch O (N) wäre, aber auch 65.536-mal so schnell wie die nicht verbesserte . Übrigens können die Fälle, in denen diese Ansätze hilfreich wären, auch von der Verwendung einer Datenstruktur mit dünnem Array profitieren, die O (AlgN) -Raum verwenden könnte, um ein Array mit einem Array der Größe N mit nicht leeren Elementen A zu halten.
Superkatze

1

Ich würde es "Lazy Array Cell Reinitialization" nennen, aber es scheint keinen etablierten Namen zu haben (das heißt, der Name ist weit verbreitet).

Der Algorithmus ist clever, aber sehr spezialisiert und auf engstem Raum anwendbar.


1

Ich glaube, es ist ein Sonderfall des Auswendiglernen , außer dass in diesem Fall die "Memos" implizit mit jedem Inkrement des globalen Zählers "altern". Ich denke eine Art "Rückwärtserinnerung".

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.