Es gibt eine Vielzahl von realisierbaren Ansätzen. Welches am besten geeignet ist, hängt davon ab
- was du versuchst zu zeigen,
- wie viel Detail Sie wollen oder brauchen.
Wenn es sich bei dem Algorithmus um einen allgemein bekannten Algorithmus handelt, den Sie als Unterroutine verwenden, bleiben Sie häufig auf einer höheren Ebene. Wenn der Algorithmus das zu untersuchende Hauptobjekt ist, möchten Sie wahrscheinlich detaillierter werden. Dasselbe gilt für Analysen: Wenn Sie eine grobe Obergrenze für die Laufzeit benötigen, verfahren Sie anders, als wenn Sie eine genaue Anzahl von Anweisungen wünschen.
Ich werde Ihnen drei Beispiele für den bekannten Algorithmus Mergesort geben, die dies hoffentlich veranschaulichen.
Hohes Level
Der Algorithmus Mergesort nimmt eine Liste, teilt sie in zwei (ungefähr) gleich lange Teile auf, rekursiert auf diese Teillisten und führt die (sortierten) Ergebnisse zusammen, so dass das Endergebnis sortiert wird. Bei einzelnen oder leeren Listen wird die Eingabe zurückgegeben.
Dieser Algorithmus ist offensichtlich ein korrekter Sortieralgorithmus. Das Aufteilen und Zusammenführen der Liste kann jeweils in der Zeit implementiert werden , was eine Wiederholung für die Worst-Case-Laufzeit T ( n ) = 2 T ( n ) ergibtΘ(n). Durch das MasterTheorem, diese auswertet bisT(n)egr& THgr;(nlogn).T(n)=2T(n2)+Θ(n)T(n)∈Θ(nlogn)
Mittleres Level
Der Algorithmus Mergesort ist durch folgenden Pseudocode gegeben:
procedure mergesort(l : List) {
if ( l.length < 2 ) {
return l
}
left = mergesort(l.take(l.length / 2)
right = mergesort(l.drop(l.length / 2)
result = []
while ( left.length > 0 || right.length > 0 ) {
if ( right.length == 0 || (left.length > 0 && left.head <= right.head) ) {
result = left.head :: result
left = left.tail
}
else {
result = right.head :: result
right = right.tail
}
}
return result.reverse
}
Wir beweisen die Richtigkeit durch Induktion. Für Listen der Länge Null oder Eins ist der Algorithmus trivial korrekt. Nehmen Sie als Induktionshypothese an, dass mergesort
bei Listen mit einer Länge von höchstens für ein willkürliches, aber festes natürliches n > 1 die Leistung korrekt ist . Nun sei L eine Liste der Länge n + 1 . Durch Induktionshypothese, und halten (nicht abnehmend) sortierte Versionen der ersten resp. zweite Hälfte von L nach den rekursiven Aufrufen. Daher wählt die Schleife in jeder Iteration das kleinste Element aus, das noch nicht untersucht wurde, und hängt es an ; somit handelt es sich um eine nicht zunehmend sortierte Liste, die alle Elemente von enthältnn>1Ln+1left
right
Lwhile
result
result
left
und right
. Die Umkehrung ist eine nicht abnehmend sortierte Version von , bei der es sich um das zurückgegebene - und gewünschte - Ergebnis handelt.L
Zählen wir zur Laufzeit Elementvergleiche und Listenoperationen (die die Laufzeit asymptotisch dominieren). Listen mit einer Länge von weniger als zwei verursachen keine. Für Listen mit einer Länge von haben wir die Operationen, die durch das Vorbereiten der Eingaben für die rekursiven Aufrufe verursacht werden, diejenigen aus den rekursiven Aufrufen selbst sowie die Schleife und eine . Beide rekursiven Parameter können mit jeweils höchstens n Listenoperationen berechnet werden. Die Schleife wird genau n Mal ausgeführt und jede Iteration verursacht höchstens einen Elementvergleich und genau zwei Listenoperationen. Das Finale kann implementiert werden, um 2 n zu verwendenn>1while
reverse
nwhile
nreverse
2nListenoperationen - Jedes Element wird aus der Eingabe entfernt und in die Ausgabeliste eingefügt. Daher erfüllt die Anzahl der Vorgänge die folgende Wiederholung:
T(0)=T(1)T(n)=0≤T(⌈n2⌉)+T(⌊n2⌋)+7n
Da eindeutig nicht abnimmt, ist es ausreichend, n = 2 k für asymptotisches Wachstum zu berücksichtigen . In diesem Fall vereinfacht sich die Wiederholung zuTn=2k
T(0)=T(1)T(n)=0≤2T(n2)+7n
Durch den Master - Satz erhalten wir , die zur Laufzeit erstreckt .T∈Θ(nlogn)mergesort
Ultra-Low-Level
Betrachten Sie diese (verallgemeinerte) Implementierung von Mergesort in Isabelle / HOL :
types dataset = "nat * string"
fun leq :: "dataset \<Rightarrow> dataset \<Rightarrow> bool" where
"leq (kx::nat, dx) (ky, dy) = (kx \<le> ky)"
fun merge :: "dataset list \<Rightarrow> dataset list \<Rightarrow> dataset list" where
"merge [] b = b" |
"merge a [] = a" |
"merge (a # as) (b # bs) = (if leq a b then a # merge as (b # bs) else b # merge (a # as) bs)"
function (sequential) msort :: "dataset list \<Rightarrow> dataset list" where
"msort [] = []" |
"msort [x] = [x]" |
"msort l = (let mid = length l div 2 in merge (msort (take mid l)) (msort (drop mid l)))"
by pat_completeness auto
termination
apply (relation "measure length")
by simp+
Dies schließt bereits Beweise für die Richtigkeit und Kündigung ein. Finden Sie einen (fast) vollständigen Korrektheitsbeweis hier .
Für die "Laufzeit", also die Anzahl der Vergleiche, kann eine ähnliche Wiederholung wie im vorherigen Abschnitt eingerichtet werden. Anstatt den Hauptsatz zu verwenden und die Konstanten zu vergessen, können Sie ihn auch analysieren, um eine Näherung zu erhalten, die asymptotisch der wahren Größe entspricht. Die vollständige Analyse finden Sie in [1]; Hier ist eine grobe Übersicht (sie muss nicht unbedingt zum Isabelle / HOL-Code passen):
Wie oben ist die Wiederholung für die Anzahl der Vergleiche
f0=f1fn=0=f⌈n2⌉+f⌊n2⌋+en
where en is the number of comparisons needed for merging the partial results². In order to get rid of the floors and ceils, we perform a case distinction over whether n is even:
{f2mf2m+1=2fm+e2m=fm+fm+1+e2m+1
Using nested forward/backward differences of fn and en we get that
∑k=1n−1(n−k)⋅Δ∇fk=fn−nf1.
The sum matches the right-hand side of Perron's formula. We define the Dirichlet generating series of Δ∇fk as
W(s)=∑k≥1Δ∇fkk−s=11−2−s⋅∑k≥1Δ∇ekks=: ⊟(s)
which together with Perron's formula leads us to
fn=nf1+n2πi∫3−i∞3+i∞⊟(s)ns(1−2−s)s(s+1)ds.
Evaluation of ⊟(s) depends on which case is analysed. Other than that, we can -- after some trickery -- apply the residue theorem to get
fn∼n⋅log2(n)+n⋅A(log2(n))+1
where A is a periodic function with values in [−1,−0.9].
- Mellin transforms and asymptotics: the mergesort recurrence by Flajolet and Golin (1992)
- Best case: en=⌊n2⌋
Worst case: en=n−1
Average case: en=n−⌊n2⌋⌈n2⌉+1−⌈n2⌉⌊n2⌋+1