Code in Mathematik übersetzen
Bei einer (mehr oder weniger) formalen operativen Semantik können Sie den (Pseudo-) Code eines Algorithmus buchstäblich in einen mathematischen Ausdruck umwandeln, der das Ergebnis liefert, vorausgesetzt, Sie können den Ausdruck in eine nützliche Form umwandeln. Dies funktioniert gut für additive Kostenmaße wie die Anzahl der Vergleiche, Auslagerungen, Anweisungen, Speicherzugriffe, Zyklen, die einige abstrakte Maschinenanforderungen usw. erfordern.
Beispiel: Vergleiche in Bubblesort
Betrachten Sie diesen Algorithmus, der ein bestimmtes Array sortiert A
:
bubblesort(A) do 1
n = A.length; 2
for ( i = 0 to n-2 ) do 3
for ( j = 0 to n-i-2 ) do 4
if ( A[j] > A[j+1] ) then 5
tmp = A[j]; 6
A[j] = A[j+1]; 7
A[j+1] = tmp; 8
end 9
end 10
end 11
end 12
A
nfor
Ccmp(n)=∑i=0n−2∑j=0n−i−21=⋯=n(n−1)2=(n2) ,
Dabei ist der Preis für jede Ausführung von Zeile 5 (die wir zählen).1
Beispiel: Swaps in Bubblesort
Ich werde bezeichne das Unterprogramm , das aus Linien besteht zu und durch die Kosten für die Ausführung dieses Unterprogrammes (einmal). C i , jPi,ji
j
Ci,j
Nehmen wir nun an, wir wollen Swaps zählen , also wie oft ausgeführt wird. Dies ist ein "Basisblock", dh ein Unterprogramm, das immer atomar ausgeführt wird und konstante Kosten verursacht (hier ). Das Zusammenziehen solcher Blöcke ist eine nützliche Vereinfachung, die wir oft anwenden, ohne darüber nachzudenken oder darüber zu sprechen. 1P6,81
Mit einer ähnlichen Übersetzung wie oben kommen wir zu folgender Formel:
Cswaps(A)=∑i=0n−2∑j=0n−i−2C5,9(A(i,j)) .
A(i,j) bezeichnet den Zustand des Arrays vor der -ten Iteration von .(i,j)P5,9
Beachten Sie, dass ich anstelle von als Parameter verwende. wir werden gleich sehen warum. Ich addiere und als Parameter von da die Kosten hier nicht davon abhängen (also im einheitlichen Kostenmodell ); im Allgemeinen könnten sie nur.AnijC5,9
Es ist klar, dass die Kosten von vom Inhalt von (den Werten und insbesondere) abhängen , daher müssen wir dies berücksichtigen. Jetzt stehen wir vor einer Herausforderung: Wie "packen" wir ? Nun, wir können die Abhängigkeit von explizit machen:P5,9AA[j]
A[j+1]
C5,9A
C5,9(A(i,j))=C5(A(i,j))+{10,A(i,j)[j]>A(i,j)[j+1],else .
Für ein bestimmtes Eingabearray sind diese Kosten genau definiert, wir möchten jedoch eine allgemeinere Aussage treffen. wir müssen stärkere Annahmen treffen. Untersuchen wir drei typische Fälle.
Der schlimmste Fall
wir nur die Summe betrachten und feststellen, dass , können wir eine unbedeutende Obergrenze für die Kosten finden:C5,9(A(i,j))∈{0,1}
Cswaps(A)≤∑i=0n−2∑j=0n−i−21=n(n−1)2=(n2) .
Aber kann das passieren , dh wird ein für diese Obergrenze erreicht? Wie sich herausstellt, ja: Wenn wir ein umgekehrt sortiertes Array von paarweise unterschiedlichen Elementen eingeben, muss bei jeder Iteration ein Swap durchgeführt werden¹. Daher haben wir die exakte Worst-Case- Anzahl von Swaps von Bubblesort abgeleitet.A
Der beste Fall
Umgekehrt gibt es eine triviale Untergrenze:
Cswaps(A)≥∑i=0n−2∑j=0n−i−20=0 .
Dies kann auch passieren: Auf einem bereits sortierten Array führt Bubblesort keinen einzelnen Swap aus.
Der durchschnittliche Fall
Am schlimmsten und bestenfalls eine ziemliche Lücke öffnen. Aber was ist die typische Anzahl von Swaps? Um diese Frage zu beantworten, müssen wir definieren, was "typisch" bedeutet. Theoretisch haben wir keinen Grund, einen Eingang einem anderen vorzuziehen, und gehen daher normalerweise von einer gleichmäßigen Verteilung über alle möglichen Eingänge aus, dh jeder Eingang ist gleich wahrscheinlich. Wir beschränken uns auf Arrays mit paarweise unterschiedlichen Elementen und nehmen daher das zufällige Permutationsmodell an .
Dann können wir unsere Kosten so umschreiben²:
E[Cswaps]=1n!∑A∑i=0n−2∑j=0n−i−2C5,9(A(i,j))
Jetzt müssen wir über die einfache Manipulation von Summen hinausgehen. Beim Betrachten des Algorithmus stellen wir fest, dass jeder Swap genau eine Inversion in (wir tauschen immer nur die Nachbarn³). Das heißt, die Anzahl von Swaps auf ausgeführt ist genau die Anzahl von Inversionen von . Somit können wir die inneren zwei Summen ersetzen und bekommenAAinv(A)A
E[Cswaps]=1n!∑Ainv(A) .
Glücklicherweise wurde die durchschnittliche Anzahl der Inversionen ermittelt
E[Cswaps]=12⋅(n2)
Welches ist unser Endergebnis. Beachten Sie, dass dies genau die Hälfte der Worst-Case-Kosten ist.
- Beachten Sie, dass der Algorithmus sorgfältig formuliert wurde, sodass "die letzte" Iteration
i = n-1
der äußeren Schleife, die niemals etwas tut, nicht ausgeführt wird.
- " " ist die mathematische Notation für "Erwartungswert", die hier nur der Durchschnitt ist.E
- Dabei lernen wir, dass kein Algorithmus, der nur benachbarte Elemente austauscht, asymptotisch schneller als Bubblesort sein kann (auch im Durchschnitt) - die Anzahl der Inversionen ist für alle derartigen Algorithmen eine Untergrenze. Dies gilt zB für Insertion Sort und Selection Sort .
Die allgemeine Methode
Wir haben im Beispiel gesehen, dass wir Kontrollstruktur in Mathematik übersetzen müssen; Ich werde ein typisches Ensemble von Übersetzungsregeln vorstellen. Wir haben auch gesehen, dass die Kosten für ein bestimmtes Unterprogramm vom aktuellen Status abhängen können, dh (ungefähr) von den aktuellen Werten von Variablen. Da der Algorithmus (normalerweise) den Status ändert, ist es etwas umständlich, die allgemeine Methode zu notieren. Wenn Sie sich verwirrt fühlen, schlage ich vor, dass Sie zum Beispiel zurückkehren oder sich selbst ein Bild machen.
Wir bezeichnen den aktuellen Status mit (stellen Sie sich dies als eine Reihe von Variablenzuweisungen vor). Wenn wir ein Programm auszuführen , beginnend im Zustand beenden wir im Zustand up (vorausgesetzt , endet).ψP
ψψ/PP
Einzelne Aussagen
Bei nur einer Anweisung S;
weisen Sie die Kosten . Dies ist normalerweise eine konstante Funktion.CS(ψ)
Ausdrücke
Wenn Sie einen Ausdruck E
der Form haben E1 ∘ E2
(z. B. einen arithmetischen Ausdruck, bei dem ∘
es sich um Addition oder Multiplikation handeln kann), addieren Sie die Kosten rekursiv:
CE(ψ)=c∘+CE1(ψ)+CE2(ψ) .
Beachten Sie, dass
- Die Betriebskosten möglicherweise nicht konstant, sondern hängen von den Werten von und und abc∘E1E2
- Die Auswertung von Ausdrücken kann den Zustand in vielen Sprachen ändern.
Daher müssen Sie möglicherweise mit dieser Regel flexibel sein.
Reihenfolge
Bei einem Programm P
als Programmfolge Q;R
addieren Sie die Kosten zu
CP(ψ)=CQ(ψ)+CR(ψ/Q) .
Bedingungen
Bei einem Programm P
der Form if A then Q else R end
hängen die Kosten vom Staat ab:
CP(ψ)=CA(ψ)+{CQ(ψ/A)CR(ψ/A),A evaluates to true under ψ,else
In der Regel kann die Auswertung A
sehr wohl den Zustand ändern, daher die Aktualisierung der Kosten für die einzelnen Filialen.
For-Loops
Weisen Sie bei einem vorgegebenen Programm P
des Formulars for x = [x1, ..., xk] do Q end
die Kosten zu
CP(ψ)=cinit_for+∑i=1kcstep_for+CQ(ψi∘{x:=xi})
Dabei ist der Zustand vor der Verarbeitung für den Wert , dh nach der Iteration mit dem Setzen auf , ..., .ψiQ
xi
x
x1
xi-1
Beachten Sie die zusätzlichen Konstanten für die Schleifenwartung. Die Schleifenvariable muss erstellt ( ) und ihre Werte zugewiesen werden ( ). Dies ist relevant seitcinit_forcstep_for
- die nächste Berechnung
xi
kann teuer sein und
- Eine
for
Schleife mit leerem Hauptteil (z. B. nach Vereinfachung in einer Best-Case-Einstellung mit bestimmten Kosten) hat keine Nullkosten, wenn sie Iterationen ausführt.
While-Schleifen
Weisen Sie bei einem vorgegebenen Programm P
des Formulars while A do Q end
die Kosten zu
CP(ψ) =CA(ψ)+{0CQ(ψ/A)+CP(ψ/A;Q),A evaluates to false under ψ, else
Durch die Überprüfung des Algorithmus kann diese Wiederholung häufig als eine ähnliche Summe wie für for-Schleifen dargestellt werden.
Beispiel: Betrachten Sie diesen kurzen Algorithmus:
while x > 0 do 1
i += 1 2
x = x/2 3
end 4
Durch die Anwendung der Regel erhalten wir
C1,4({i:=i0;x:=x0}) =c<+{0c+=+c/+C1,4({i:=i0+1;x:=⌊x0/2⌋}),x0≤0, else
mit einigen konstanten Kosten für die einzelnen Anweisungen. Wir gehen implizit davon aus, dass diese nicht vom Zustand abhängen (die Werte von und ); Dies mag in der "Realität" zutreffen oder auch nicht: Denken Sie an Überläufe!c…i
x
Jetzt müssen wir diese Wiederholung für lösen . Wir stellen fest, dass weder die Anzahl der Iterationen noch die Kosten des Schleifenkörpers vom Wert von abhängen , sodass wir ihn fallen lassen können. Wir bleiben mit dieser Wiederholung:C1,4i
C1,4(x)={c>c>+c+=+c/+C1,4(⌊x/2⌋),x≤0, else
Dies löst mit elementaren Mitteln zu
C1,4(ψ)=⌈log2ψ(x)⌉⋅(c>+c+=+c/)+c> ,
symbolisch den vollen Zustand wieder einführen; Wenn , dann ist .ψ={…,x:=5,…}ψ(x)=5
Prozeduraufrufe
Weisen Sie bei einem Programm P
der Form M(x)
für einige Parameter, bei x
denen M
es sich um eine Prozedur mit einem (benannten) Parameter p
handelt, Kosten zu
CP(ψ)=ccall+CM(ψglob∘{p:=x}) .
Beachten Sie noch einmal die zusätzliche Konstante (die tatsächlich von abhängen kann !). Prozeduraufrufe sind teuer, da sie auf realen Maschinen implementiert sind und manchmal sogar die Laufzeit dominieren (z. B. naives Auswerten der Fibonacci-Nummernwiederholung).ccallψ
Ich beschreibe einige semantische Probleme, die Sie möglicherweise mit dem Staat haben. Sie möchten den globalen Status und solche lokalen Prozeduraufrufe unterscheiden. Nehmen wir an, wir übergeben hier nur den globalen Status und erhalten M
einen neuen lokalen Status, der durch Setzen des Werts auf " p
to" initialisiert wird x
. Darüber hinaus x
kann es sich um einen Ausdruck handeln, von dem wir (normalerweise) annehmen, dass er vor dem Bestehen ausgewertet wird.
Beispiel: Betrachten Sie die Vorgehensweise
fac(n) do
if ( n <= 1 ) do 1
return 1 2
else 3
return n * fac(n-1) 4
end 5
end
Gemäß den Regeln erhalten wir:
Cfac({n:=n0})=C1,5({n:=n0})=c≤+{C2({n:=n0})C4({n:=n0}),n0≤1, else=c≤+{creturncreturn+c∗+ccall+Cfac({n:=n0−1}),n0≤1, else
Beachten Sie, dass wir den globalen Status außer Acht lassen, da auf fac
keinen eindeutig zugegriffen wird. Diese besondere Wiederholung ist leicht zu lösen
Cfac(ψ)=ψ(n)⋅(c≤+creturn)+(ψ(n)−1)⋅(c∗+ccall)
Wir haben die Sprachfunktionen, die Ihnen begegnen werden, in einem typischen Pseudocode behandelt. Passen Sie bei der Analyse von Pseudocodes auf hoher Ebene auf versteckte Kosten auf. im Zweifelsfall aufklappen. Die Notation mag umständlich erscheinen und ist sicherlich Geschmackssache; Die aufgeführten Konzepte können jedoch nicht ignoriert werden. Mit etwas Erfahrung können Sie jedoch sofort erkennen, welche Teile des Staates für welches Kostenmaß relevant sind, zum Beispiel "Problemgröße" oder "Anzahl der Eckpunkte". Der Rest kann fallengelassen werden - das vereinfacht die Sache erheblich!
Wenn Sie jetzt denken, dass dies viel zu kompliziert ist, seien Sie gewarnt: es ist ! Es ist eine schwierige Aufgabe, die genauen Kosten von Algorithmen in jedem Modell abzuleiten, das sich so nah an realen Maschinen befindet, dass Laufzeitvorhersagen (auch relativ) möglich sind. Dabei werden Caching und andere unangenehme Effekte auf realen Maschinen nicht berücksichtigt.
Daher wird die Algorithmusanalyse häufig so vereinfacht, dass sie mathematisch nachvollziehbar ist. Wenn Sie zum Beispiel keine genauen Kosten benötigen, können Sie zu jedem Zeitpunkt (für obere bzw. untere Grenzen) über- oder unterschätzen: Reduzieren Sie die Menge der Konstanten, beseitigen Sie die Bedingungen, vereinfachen Sie die Summen und so weiter.
Ein Hinweis zu den asymptotischen Kosten
Was Sie normalerweise in der Literatur und im Internet finden, ist die "Big-Oh-Analyse". Der eigentliche Begriff ist asymptotische Analyse. Das bedeutet, dass Sie anstelle der exakten Ableitung der Kosten wie in den Beispielen nur Kosten bis zu einem konstanten Faktor und im Grenzbereich angeben (grob gesagt "für großes ").n
Dies ist (oft) gerecht, da abstrakte Aussagen in der Realität einige (im Allgemeinen unbekannte) Kosten verursachen, abhängig von Maschine, Betriebssystem und anderen Faktoren, und kurze Laufzeiten möglicherweise von dem Betriebssystem dominiert werden, das den Prozess überhaupt erst einrichtet, und so weiter. Sie stören also sowieso.
Hier ist, wie die asymptotische Analyse mit diesem Ansatz zusammenhängt.
Identifizieren Sie dominante Vorgänge (die Kosten verursachen), dh Vorgänge, die am häufigsten auftreten (bis zu konstanten Faktoren). Im Bubblesort-Beispiel ist eine mögliche Auswahl der Vergleich in Zeile 5.
Alternativ binden Sie alle Konstanten für Elementaroperationen an ihr Maximum (von oben) resp. ihr Minimum (von unten) und führen Sie die übliche Analyse.
- Führen Sie die Analyse unter Verwendung der Ausführungszahlen dieser Operation als Kosten durch.
- Lassen Sie bei der Vereinfachung Schätzungen zu. Achten Sie darauf, Schätzungen von oben nur dann zuzulassen, wenn Ihr Ziel eine Obergrenze ( ) bzw. von unten, wenn Sie untere Schranken ( ) wollen.OΩ
Stellen Sie sicher, dass Sie die Bedeutung der Landau-Symbole verstehen . Denken Sie daran, dass solche Grenzen für alle drei Fälle existieren . Die Verwendung von impliziert keine Worst-Case-Analyse.O
Weitere Lektüre
In der Algorithmusanalyse gibt es noch viele weitere Herausforderungen und Tricks. Hier finden Sie einige Leseempfehlungen.
Es gibt viele Fragen zur Algorithmus-Analyse , die ähnliche Techniken verwenden.