Amortisierte Analyse? (Garantien im schlimmsten Fall)

12

Was ist Amortized Analysis? Und wie kann es mir helfen , in meinen Programmen Worst-Case-Leistungsgarantien zu erzielen ?

Ich habe gelesen, dass die folgenden Techniken dem Programmierer helfen können, Garantien für die Leistung im schlimmsten Fall zu erzielen (dh in meinen eigenen Worten: Gewährleisten, dass die Laufzeit eines Programms die Laufzeit in der schlechtesten Besetzung nicht überschreitet):

  • Randomisierte Algorithmen (zB Quicksort-Algorithmus ist im schlimmsten Fall quadratisch, aber zufällige Reihenfolge der Eingabe gibt eine Wahrscheinlichkeitsgarantie dafür, dass die Laufzeit linearithmisch ist)
  • Abläufe (unsere Analyse muss sowohl die Daten als auch den Ablauf der vom Kunden durchgeführten Abläufe berücksichtigen)
  • Amortized Analysis (eine andere Möglichkeit, eine Leistungsgarantie zu geben, besteht darin, die Kosten zu amortisieren, indem die Gesamtkosten aller Vorgänge geteilt durch die Anzahl der Vorgänge nachverfolgt werden. In dieser Einstellung können wir einige kostspielige Vorgänge zulassen, während die durchschnittlichen Kosten beibehalten werden.) Mit anderen Worten, wir verteilen die Kosten der wenigen teuren Vorgänge, indem wir einen Teil davon einer großen Anzahl kostengünstiger Vorgänge zuweisen.)

Der Autor erwähnte die Verwendung der Größenänderung der Array-Datenstruktur für Stack als ein Beispiel dafür, wie eine amortisierte Analyse erzielt werden kann, aber ich verstehe immer noch nicht, was eine amortisierte Analyse ist und wie sie tatsächlich implementiert werden kann (Datenstruktur-? Algorithmus?), Um das Schlechteste zu erreichen Leistungsgarantien

Anthony
quelle

Antworten:

13

Sie implementieren keine amortisierte Analyse. Es ist eine Technik, um genauere OGrenzen zu erhalten.

Die wesentliche Beobachtung, die Sie machen müssen, ist, dass teure Operationen zu keiner Zeit stattfinden können.

Bei einer Array-gestützten Datenstruktur muss die Größe des Arrays von Zeit zu Zeit geändert werden - wenn es voll ist. Dies ist der teuerste Vorgang und benötigt O(n)Zeit. Alle anderen Einfügungen in das Array sind O(1).

Um die Laufzeit zum Einfügen von nElementen zu bestimmen , können Sie nmit der teuersten Operation multiplizieren O(n), was zu einem Gesamtlaufzeitverhalten von führt O(n^2).

Dies ist jedoch ungenau, da die Größenänderung nicht sehr häufig vorkommt.

Wenn Sie über Geld sprechen, amortisieren Sie die Kosten, wenn Sie Ihre Schulden mit mehreren kleinen Zahlungen im Laufe der Zeit abbezahlen.

Mit diesem Modell können wir auch über Algorithmen nachdenken. Wir ersetzen einfach "Zeit" durch "Geld", um mentales Mapping zu vermeiden.

Sobald das Array seine Länge erreicht hat n, können wir seine Größe verdoppeln. Wir müssen die folgenden Operationen durchführen:

  • Ordnen Sie 2nSpeicherbereiche zu
  • nElemente kopieren

Wenn wir davon ausgehen, dass sowohl das Zuweisen von Speicher als auch das Kopieren in linearer Zeit erfolgen, ist dies ein sehr teurer Vorgang. Jetzt können wir jedoch die Idee der Verschuldung verwenden und sie für unsere Analyse amortisieren. Nur werden wir unsere Schulden amortisieren, bevor wir sie tatsächlich machen.
Nehmen wir an, dass unser Kontostand (von Geld / Zeit) wieder 0 ist (dh wir haben weder Schulden noch Reste), sobald wir die Größe des Arrays geändert haben.

Dies hat folgende Auswirkungen:

  • Durch das Einfügen der nächsten nElemente werden die Kosten für das Ändern der Größe und das Kopieren gedeckt (wir haben nSteckplätze und nnicht verwendete Steckplätze verwendet).

Wir können uns nun überlegen, wofür jede Insert-Operation bezahlt werden muss:

  • Der Einsatz
  • Die Kosten für die Zuweisung eines Speicherbereichs
  • die Kosten für das Verschieben in den neu zugewiesenen Speicher

Wir haben jetzt die Kosten für die Speicherzuweisung, das Kopieren und das Einfügen der nächsten nElemente übernommen. Wir haben es jedoch bisher versäumt, den alten nElementen Speicherplatz zuzuweisen und sie zu kopieren.

Wir verteilen einfach die Kosten unserer alten nElemente auf unsere neuen (noch einzufügenden) nElemente:

  • Die Kosten für die Zuweisung eines Speicherbereichs
  • die Kosten für das Verschieben in den neu zugewiesenen Speicher

Insgesamt kostet jede Insert-Operation 5 Einheiten. Dies zahlt sich für das eigene Einfügen und das Verschieben und Zuweisen von Raum für sich selbst und eines der alten Elemente aus.

Jeder Einfügevorgang benötigt immer noch eine konstante Zeit, aber die Größenänderung ist kostenlos: Wir haben sie amortisiert, indem wir "mehr" Zeit für jedes Einfügen aufgewendet haben.

Das Einfügen von nElementen nimmt daher O(n)Zeit in Anspruch .

Weitere Techniken für die Amortisationsanalyse werden hier erläutert .

phant0m
quelle
1

Zuallererst: Es ist eine Technik zum Analysieren von Programmlaufzeiten, keine Implementierungstechnik für Algorithmen.

Das in Ihrer Liste erwähnte Beispiel ist gut: Ein einzelnes Element an eine Array-gestützte Datenstruktur anhängen. Im schlimmsten Fall müssen für jede einzelne Anfügungsoperation alle vorhandenen Elemente kopiert werden. Diese Art der Analyse ist viel zu pessimistisch, da Sie dies nicht tun müssen, wenn Sie eine vernünftige Größenänderungsstrategie verwenden (Multiplikation der Größe mit etwas x> 1,0). Die Analyse besagt dann, dass Sie eine O (n ^ 2) -Grenze haben - O (n) pro Artikel mal n Artikel -, während die tatsächliche Laufzeit nur O (n) ist.

Wenn Sie die Kosten für die Größenänderung über alle eingefügten Elemente mitteln (von denen die meisten keine Größenänderung erfordern), führen Sie eine Amortisationsanalyse durch. Die amortisierte Analyse führt zu einer O (n) -Grenze, die dem tatsächlichen Verhalten des Algorithmus entspricht.

Patrick
quelle