Esiste un'enorme varietà di approcci fattibili. Da quale dipende il più adatto
- cosa stai cercando di mostrare,
- quanti dettagli vuoi o hai bisogno.
Se l'algoritmo è ampiamente noto e utilizzato come subroutine, spesso si rimane a un livello superiore. Se l'algoritmo è l'oggetto principale oggetto di indagine, probabilmente vorrai essere più dettagliato. Lo stesso si può dire per le analisi: se è necessario un limite di runtime superiore approssimativo, si procede in modo diverso da quando si desidera un conteggio preciso delle istruzioni.
Vi darò tre esempi per il noto algoritmo Mergesort che, si spera, possa illustrarlo.
Alto livello
L'algoritmo Mergesort prende un elenco, lo divide in due (circa) parti ugualmente lunghe, ricorre su quegli elenchi parziali e unisce i risultati (ordinati) in modo che il risultato finale sia ordinato. Sugli elenchi singleton o vuoti, restituisce l'input.
Questo algoritmo è ovviamente un algoritmo di ordinamento corretto. La suddivisione dell'elenco e la sua fusione possono essere implementate ciascuna nel tempo , il che ci dà una ricorrenza per il peggior tempo di esecuzione T ( n ) = 2 T ( nΘ(n). Secondo il teorema del Maestro, questo valutaT(n)∈Θ(nlogn).T(n)=2T(n2)+Θ(n)T(n)∈Θ(nlogn)
Livello medio
L'algoritmo Mergesort è dato dal seguente pseudo-codice:
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
}
Dimostriamo correttezza per induzione. Per elenchi di lunghezza zero o uno, l'algoritmo è banalmente corretto. Come ipotesi di induzione, supponiamo che mergesort
funzioni correttamente su liste di lunghezza al massimo per alcuni n > 1 arbitrari, ma fissi . Ora lascia che L sia un elenco di lunghezza n + 1 . Per ipotesi di induzione, e tenere versioni (non decrescenti) ordinate del primo resp. seconda metà di L dopo le chiamate ricorsive. Pertanto, il ciclo seleziona in ogni iterazione l'elemento più piccolo non ancora studiato e lo aggiunge a ; quindi è un elenco non sempre più ordinato che contiene tutti gli elementi dinn>1Ln+1left
right
Lwhile
result
result
left
e right
. Il contrario è una versione non decrescente di , che è il risultato restituito - e desiderato -.L
Per quanto riguarda il runtime, contiamo i confronti degli elementi e le operazioni di elenco (che dominano il runtime in modo asintotico). Elenchi di lunghezza inferiore a due non causano nessuno. Per elenchi di lunghezza , abbiamo quelle operazioni causate dalla preparazione degli input per le chiamate ricorsive, quelle dalle chiamate ricorsive stesse più il loop e uno . Entrambi i parametri ricorsivi possono essere calcolati con al massimo n operazioni di lista ciascuna. Il ciclo viene eseguito esattamente n volte e ogni iterazione provoca al massimo un confronto tra elementi e esattamente due operazioni di elenco. Il finale può essere implementato per utilizzare 2 nn>1while
reverse
nwhile
nreverse
2noperazioni dell'elenco: ogni elemento viene rimosso dall'input e inserito nell'elenco di output. Pertanto, il conteggio delle operazioni soddisfa la seguente ricorrenza:
T(0)=T(1)T(n)=0≤T(⌈n2⌉)+T(⌊n2⌋)+7n
Poiché è chiaramente non decrescente, è sufficiente considerare n = 2 k per la crescita asintotica. In questo caso , la ricorrenza si semplificaTn=2k
T(0)=T(1)T(n)=0≤2T(n2)+7n
Con il teorema del Maestro, otteniamo che si estende al tempo di esecuzione di .T∈Θ(nlogn)mergesort
Livello ultra basso
Considera questa implementazione (generalizzata) di 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+
Ciò include già prove di correttezza e risoluzione. Trova una (quasi) completa prova di correttezza qui .
Per il "runtime", ovvero il numero di confronti, è possibile impostare una ricorrenza simile a quella nella sezione precedente. Invece di usare il teorema del Maestro e dimenticare le costanti, puoi anche analizzarlo per ottenere un'approssimazione asintoticamente uguale alla quantità reale. Puoi trovare l'analisi completa in [1]; ecco un profilo approssimativo (non si adatta necessariamente al codice Isabelle / HOL):
Come sopra, la ricorrenza per il numero di confronti è
f0=f1fn=0=f⌈n2⌉+f⌊n2⌋+en
enn
{f2mf2m+1=2fm+e2m=fm+fm+1+e2m+1
fnen
∑k=1n−1(n−k)⋅Δ∇fk=fn−nf1
Δ∇fk
W(s)=∑k≥1Δ∇fkk−s=11−2−s⋅∑k≥1Δ∇ekks=: ⊟(s)
che insieme alla formula di Perron ci porta a
fn=nf1+n2πi∫3−i∞3+i∞⊟(s)ns(1−2−s)s(s+1)ds
⊟(s)
fn∼n⋅log2(n)+n⋅A(log2(n))+1
A[−1,−0.9]
- Trasformazioni e asintotiche di Mellin: la fusione di ricorrenza di Flajolet e Golin (1992)
- en=⌊n2⌋
en=n−1
en=n−⌊n2⌋⌈n2⌉+1−⌈n2⌉⌊n2⌋+1