Disclaimer: Diese Mitschrift ist als Ergänzung zur Vorlesung gedacht. Wir erheben keinen Anspruch auf Vollständigkeit und Korrektheit. Wir sind jederzeit froh um Hinweise zu Fehlern oder Unklarheiten an grafdan@ethz.ch.
Wenn Sie an dieser Mitschrift mitarbeiten möchten, melden Sie sich hier.
Heute behandeln wir die Analysetechnik der amortisierten Analyse. Ein erstes Mal hatten wir diese bereits gesehen, als wir argumentiert haben, dass Quicksort nie mehr als
upin
-Aufwand im AVL-BaumErinnern wir uns daran, was wir zum AVL-Baum schon gesehen haben:
Suche im AVL-Baum:
Einfügen: Suchen + upin
Wie weit muss das upin
wirklich hochpropagieren? In einer einzigen Einfügeoperation kann es durchaus sein, dass wir bis ganz noch oben zur Wurzel zurück suchen müssen und upin
logarithmisch viele Schritte braucht. (Rotationen braucht es aber immer nur eine.) Aber was wir heute zeigen wollen ist, dass dies nicht immer wieder passieren kann. Wir wollen also zeigen, dass auf lange Sicht, also über viele Operationen hinweg, im Schnitt nicht logarithmisch viel Aufwand für upin
nötig ist.
Wir betrachten den Worst Case über eine Folge von Operationen. Wir beginnen mit einem leeren Baum und führen
Wir schauen unseren AVL-Baum an und sagen, dass ein Knoten in strikter Balance ist, wenn seine beiden Teilbäum genau gleich hoch sind. Wir legen nun einen fiktiven Dollar auf jeden Knoten, der in strikter Balance liegt. Dieser Dollar soll nun dafür bezahlen, wenn wir später über diesen Knoten hinweg mit upin
hochlaufen.
Wie wir gesehen haben, müssen wir upin
nur rekursiv aufrufen, wenn der Knoten vor dem Einfügen in strikter Balance war. Bei jedem Knoten in strikter Balance, liegt ein Dollar, mit dem wir das Hochprogieren bezahlen können. Da wir nur weiter aufsteigen, wenn wir die Balance von upin
entweder, weil die Balance neu direkt upin
endet. Pro Einfügeoperation müssen wir also nur
Als Analogie können wir uns das so vorstellen: Die Münzen in unserem AVL-Baum sind unsere Sparkasse, die uns hilft teure Operationen abzuzahlen. Alles was wir nicht aus der Sparkasse bezahlen können und alles was wir in die Sparkasse einwerfen, kommt aus unserem Geldbeutel. Die amortisierte Analyse erlaubt uns nun unseren Geldbeutel möglichst gleichmässig zu belasten, hier heisst das, dass wir in jedem Schritt nur einen konstanten Betrag ausgeben.
Ein anderes (einfacheres) Beispiel für die amortisierte Anlyse sei ein Stapel (engl. Stack). Wir können mit push
in pop
entfernen. Wir erlauben nun eine weitere Operation multipop(k)
, die so lange Elemente entfernt, bis ein Element k
entfernt wird. Für einen Stapel mit multipop(k)
lineare Laufzeit
|2|
|8|
|2| -> multipop(7) -> |5|
|7|
|5|
Betrachten wir eine Folge von multipop
nicht
Wir machen diese Intuition formal, indem wir bei jedem Einfügen noch einen zusätzlichen Dollar auf den Stapeleintrag hinlegen, der dann fürs spätere pop
, bzw. fürs multipop
des Elementes bezahlt. Alle späteren pop
und multipop
sind dann also quasi schon bezahlt.
Also hat push
amortisierte Kosten pop
und multipop
amortisierte Kosten
Machen wir es noch etwas formaler. Wir können, den Kontostand auch als Invariante betrachten. Dazu definieren wir eine Kontostandsfunktion:
Wenn wir den Kontostand geschickt wählen, müssen wir somit nur noch überprüfen, dass wir die gewünschten amortisierten Kosten push
ist
Wir beginnen bei
Wenn wir um eins erhöhen beginnen wir rechts und flippen die Bits der Reihe nach, bis wir eine Null in eine Eins flippen.
Es ist klar, dass wir höchstens
Eine Art das zu sehen ist die Folgende: Jedes zweite Hochzählen braucht nur einen Flip, denn die erste Stelle ist ja immer abwechslungsweise
In unserem Goldmünzenmodell ist die Argumentation noch einfacher. Wir sagen, dass wir jedes Mal wenn wir eine
Wir haben eine lineare Liste in der wir nach Schlüsseln suchen können. Wir erlauben kein Einfügen oder Entfernen, aber wir wollen die Analyse etwas genauer machen als einfach nur zu sagen "im schlimmsten Fall ist alles linear".
-> a -> b -> c -> d
Nehmen wir an, dass wir sehr verschieden häufig nach den verschiedenen Schlüssel in der Liste suchen. Es gibt sozusagen "Lieblingsschlüssel" nach denen besonders häufig gesucht wird, z.B sucht man in einer Personendatenbank der ETH vielleicht am öftesten nach der Rektorin, einzelne Studenten werden hingegen selten gesucht.
Können wir die Listenstruktur nun etwas umarrangieren, sodass die Suche schneller geht?
Erste Idee: Wenn ich die Häufigkeiten kenne, kann ich die Liste einfach nach Häufigkeit absteigend sortieren. Zuvorderst der häufigste Schlüssel, dann der zweithäufigste etc. Dann hab ich für den "beliebtesten" Knoten nur Kosten
Aber was machen wir wenn wir die Häufigkeiten der Schlüssel nicht kennen?
Wir könnten das gesuchte Element jeweils ein wenig nach vorne rücken, sodass es, wenn es immer wieder gesucht wird, mit der Zeit am Anfang der Liste landet. Diese Idee namens transpose tauscht also immer den gesuchten Listeneintrag mit seinem Vorgänger. Am teuersten wird dies, wenn man immer nach dem letzten Element sucht.
Beispiel
-> ... -> y -> x
(Suche nach x)
-> ... -> x -> y
(Suche nach y)
-> ... -> y -> x
(Suche nach x)
-> ... -> x -> y
(Suche nach y)
-> ... -> y -> x
...
Für
Eine solche Strategie heisst move to front und setzt jeweils beim Zugriff das Element ganz an den Anfang der Liste.
-> y -> ... -> x ->... (wir suchen nach x)
-> x -> y --------> (wir setzen x an den Anfang)
Das teure Beispiel von transpose funktioniert hier jetzt nicht mehr. Nach zwei Operationen sind
Wir vergleichen unsere Methode jetzt mit einem idealen Konkurrenten
Wir nehmen dabei an, dass unser Konkurrent so wie Move to Front nur das zugegriffene Element
Wir betrachten als Kosten die Anzahl Vertauschungen in der Liste. Dabei sagen wir, dass das Vertauschen von
Behauptung: Move to front ist nicht schlechter als ein idealer Konkurrent.
Unser Verfahren
Als Kontostand nehmen wir die Anzahl der Inversionen zwischen den beiden Listen, also die Anzahl Paare, die in
A 4 2 3 1 5
MTF 2 3 1 4 5
Dieses Beispiel hat drei Inversionen (
A 1 2 3 4 5
MTF 2 3 4 1 5
Somit steht in
Was passiert mit unserem Kontostand, der Anzahl Inversionen?
Dazu bezeichnen wir mit
Nun gilt für die amortisierten Kosten von
wobei
Daraus folgt nun, wenn wir über alle Operationen summieren
Somit sind die Kosten von Move to Front gesamthaft höchstens das Doppelte von