Il existe une grande variété d'approches possibles. Ce qui convient le mieux dépend de
- ce que vous essayez de montrer,
- combien de détails vous voulez ou avez besoin.
Si l'algorithme est largement connu et que vous utilisez comme sous-programme, vous restez souvent à un niveau supérieur. Si l'algorithme est l'objet principal étudié, vous voudrez probablement être plus détaillé. La même chose peut être dite pour les analyses: si vous avez besoin d'une limite d'exécution supérieure approximative, vous procédez différemment de lorsque vous voulez un décompte précis des instructions.
Je vais vous donner trois exemples pour l'algorithme bien connu Mergesort qui, espérons-le, illustrent cela.
Haut niveau
L'algorithme Mergesort prend une liste, la divise en deux parties (à peu près) également longues, revient sur ces listes partielles et fusionne les résultats (triés) afin que le résultat final soit trié. Sur des listes singleton ou vides, il renvoie l'entrée.
Cet algorithme est évidemment un algorithme de tri correct. Le fractionnement de la liste et sa fusion peuvent chacun être mis en œuvre dans le temps , ce qui nous donne une récurrence pour le pire cas d'exécution T ( n ) = 2 T ( nΘ(n). Par le théorème maître, cela équivaut àT(n)∈Θ(nlogn).T(n)=2T(n2)+Θ(n)T(n)∈Θ(nlogn)
Niveau moyen
L'algorithme Mergesort est donné par le pseudo-code suivant:
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
}
Nous prouvons l'exactitude par induction. Pour les listes de longueur zéro ou un, l'algorithme est trivialement correct. Comme hypothèse d'induction, supposons qu'il mergesort
fonctionne correctement sur des listes de longueur au plus pour certains n naturels arbitraires mais fixes n > 1 . Soit maintenant L une liste de longueur n + 1 . Par hypothèse d'induction, et maintenez (sans diminution) les versions triées du premier resp. seconde moitié de L après les appels récursifs. Par conséquent, la boucle sélectionne à chaque itération le plus petit élément non encore étudié et l'ajoute à ; est donc une liste non triée de plus en plus contenant tous les éléments denn>1Ln+1left
right
Lwhile
result
result
left
et right
. L'inverse est une version triée de façon non décroissante de , qui est le résultat renvoyé - et souhaité -.L
Quant à l'exécution, comptons les comparaisons d'éléments et listons les opérations (qui dominent l'exécution de manière asymptotique). Les listes de longueur inférieure à deux ne provoquent ni l'un ni l'autre. Pour les listes de longueur , nous avons ces opérations causées par la préparation des entrées pour les appels récursifs, celles des appels récursifs eux-mêmes plus la boucle et une . Les deux paramètres récursifs peuvent être calculés avec au plus n opérations de liste chacune. La boucle est exécutée exactement n fois et chaque itération provoque au plus une comparaison d'éléments et exactement deux opérations de liste. La finale peut être implémentée pour utiliser 2 nn>1while
reverse
nwhile
nreverse
2nopérations de liste - chaque élément est supprimé de l'entrée et placé dans la liste de sortie. Par conséquent, le nombre d'opérations remplit la récurrence suivante:
T(0)=T(1)T(n)=0≤T(⌈n2⌉)+T(⌊n2⌋)+7n
Comme est clairement non décroissant, il suffit de considérer n = 2 k pour une croissance asymptotique. Dans ce cas , la récurrence se simplifie pourTn=2k
T(0)=T(1)T(n)=0≤2T(n2)+7n
Par le théorème maître, nous obtenons qui s'étend à l'exécution de .T∈Θ(nlogn)mergesort
Niveau ultra bas
Considérez cette implémentation (généralisée) de Mergesort dans 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+
Cela comprend déjà des preuves de bonne définition et de résiliation. Trouvez ici une preuve d'exactitude (presque) complète .
Pour le "runtime", c'est-à-dire le nombre de comparaisons, une récurrence similaire à celle de la section précédente peut être mise en place. Au lieu d'utiliser le théorème maître et d'oublier les constantes, vous pouvez également l'analyser pour obtenir une approximation qui est asymptotiquement égale à la vraie quantité. Vous pouvez trouver l'analyse complète dans [1]; voici un aperçu (il ne correspond pas nécessairement au code Isabelle / HOL):
Comme ci-dessus, la récurrence du nombre de comparaisons est
f0=f1fn=0=f⌈n2⌉+f⌊n2⌋+en
enn
{f2mf2m+1=2fm+e2m=fm+fm+1+e2m+1
Utilisation de différences imbriquées avant / arrièrefnen
∑k=1n−1(n−k)⋅Δ∇fk=fn−nf1
Δ∇fk
W(s)=∑k≥1Δ∇fkk−s=11−2−s⋅∑k≥1Δ∇ekks=: ⊟(s)
qui, avec la formule de Perron, nous amène à
fn=nf1+n2πi∫3−i∞3+i∞⊟(s)ns(1−2−s)s(s+1)ds
Évaluation de ⊟(s)
fn∼n⋅log2(n)+n⋅A(log2(n))+1
A[−1,−0.9]
- Transformées de Mellin et asymptotiques: la récurrence de fusion-fusion par Flajolet et Golin (1992)
- Meilleur cas: en=⌊n2⌋
en=n−1
en=n−⌊n2⌋⌈n2⌉+1−⌈n2⌉⌊n2⌋+1