• Aucun résultat trouvé

Recherche d’un motif dans une chaîne de caractères

Figure 3.3 – Différents logarithmes

Algorithme d’exponentiation rapide. Cet algorithme de calcul de xn effectue autant de tours de boucle que n

a de chiffres en binaire, et chaque tour de boucle se fait avec une complexité constante. On en déduit une complexité O(log2(n)). Remarquez que la base du logarithme importe peu car les logarithmes dans deux bases distinctes ne diffèrent que d’une constante multiplicative, ignorée par la notation O, on peut donc noter la complexité O(log n) sans préciser la base.

Algorithme de recherche dichotomique dans une liste triée. Là aussi chaque tour de boucle se fait avec une complexité constante, il reste à estimer le nombre de tours de boucle effectués. Notons n la taille de la liste et di et

gi les valeurs des variables d et g après i tour de boucle, et ti = di− gi. Initialement, d0= n et g0= 0, donc t0= n.

Distinguons les cas :

— si d prend la valeur m après un tour de boucle supplémentaire, on a di+1 =

jd i+gi 2 k ≤ di+gi 2 et gi+1 = gi, donc

ti+1= di+1− gi+1≤ di−g2 i = t2i;

— si g prend la valeur m + 1 après un tour de boucle supplémentaire, on a gi+1 = 1 +

j di+gi 2 k ≥di+gi+1 2 (car gi+di 2

est un entier ou un demi-entier) et di+1= di, donc ti+1≤ di−g2i−1 ≤t2i;

Ainsi, on vérifie aisément par récurrence que tk ≤ 2nk, et ce terme est donc nul pour k > log2(n). Comme la boucle

s’arrête lorsque d − g = 0, on conclut que le nombre d’opérations effectuées est O(log n).

3.3.4

Quelques ordres de grandeur

Le tableau suivant présente le n maximal tel qu’un algorithme nécessitant f (n) opérations élémentaires pour s’exécuter sur l’entrée n puisse s’exécuter en moins d’une minute, et ce pour plusieurs fonctions f . On suppose que l’algorithme exécute exactement 109 opérations élémentaires par seconde. Cela donne une indication de ce que peut

faire un algorithme de complexité O(f (n)).

f (n) ln(n) √n n n2 n3 2n n! nn nmax très gros ! 3.6 × 1021 6 × 1010 244948 3914 35 13 10

Naturellement, plus la fonction f grandit, plus nmax est faible. On pourra remarquer le fossé entre une fonction

polynomiale (une fonction de la forme n 7→ nk) et les fonctions au moins exponentielles (les trois dernières). On parle

de complexité linéaire, quadratique, cubique pour des algorithmes de complexité O(n), O(n2), O(n3) et de complexité

(au moins) exponentielle pour les trois dernières fonctions. Un algorithme de complexité au moins exponentielle en n n’est en pratique utilisable que pour de très petites valeurs de n.

3.4

Recherche d’un motif dans une chaîne de caractères

On s’intéresse au problème de la recherche de motif dans une chaîne. On dit quem est un motif de s si m coïncide avecs[i:k] pour deux indices i et k. Pour tester si s possède m comme motif, il suffit de vérifier si la proposition suivante est vraie :

il existe un indicei entre 0 et len(s)-len(m) (inclus), tel que pour tout j entre 0 et len(m)-1, s[i+j] est égal à m[j].

On voit se dessiner une idée d’algorithme : il suffit de faire parcourir ài toutes les valeurs entre 0 et len(s)-len(m), et ensuite incrémenter un compteur j commençant à 0, tant que s[i+j] est égal à m[j]. Si j atteint len(m), on a trouvé un tel motif, sinon on recommence avec lei suivant. Voici le code Python correspondant à cette idée :

Algorithme de recherche de motif dans une chaîne de caractères def recherche_motif(m,s):

""" La fonction prend en entrée deux chaînes de caractères m et s, et retourne True si m apparaît comme sous-chaîne de s, False sinon.""" lm=len(m)

ls=len(s)

for i in range(0,ls-lm+1):

#Inv(i): m n'apparaît pas comme motif de la sous-chaîne s[0:i+lm-1]

j=0

#Inv2: Les sous-chaînes m[0:j] et s[i:i+j] sont égales

while j<lm and m[j]==s[i+j]:

#Inv2 j+=1 if j==lm: return True #Inv(i+1) return False

Montrons que l’algorithme est correct. On notera `m et `s les longueurs des chaînes m et s.

Tout d’abord, on n’essaiera jamais d’accéder à un élément d’une chaîne au delà de sa longueur : en effet, on essaie d’accéder à m[j] que si j < `m, en vertu du caractère paresseux du and : si j < `m est faux (donc j ≥ `m), alors

on n’a pas besoin d’évaluerm[j]==s[i+j]. De même, on n’accède à s[i+j] qu’avec i < `s− `m+ 1 et j < `mdonc

i + j < `s.

L’algorithme termine bien, car la boucle while interne termine à chaque fois : `m− j est une quantité qui reste

positive et décroît strictement à chaque passage dans la boucle.

L’invariant proposé pour la bouclewhile interne est correct : il est vrai avant la boucle puisque les chaînes m[0:0] et s[i:i] sont vides. Si on est encore dans la boucle cela signifie que les deux chaînes considérées coïncident sur un caractère supplémentaire.

Pour la boucle externe, l’invariant proposé est correct car il repose sur l’invariant de la bouclewhile. En sortie de bouclewhile, l’invariant est vrai : si j = `malorsm est un motif de s (en effet, m=s[i:i+lm]), et on renvoie True, donc

la sortie de la fonction est correcte. Sinon, on est sorti de la bouclewhile parce que m[j] était différent de s[i+j] et m et s[i:i+lm] ne coïncide pas, et l’invariant est vrai en bas de la boucle.

Ainsi, si l’on atteint le bas de la bouclefor, cela signifie que m n’apparaît pas comme motif de s[0:ls-lm+1+lm-1] qui est égal às[0:ls] donc à s. On renvoie False et la fonction est correcte.

Examinons le coût de la fonction : la boucle while interne réalise au plus `m tours de boucle à chaque étape.

La boucle for fait exactement (`s− `m+ 1) étapes (si `s ≥ `m, sinon 0), on en déduit une complexité totale de

O(1 + `m(`s− `m+ 1)) (le 1 devant est fait pour que la formule soit vraie même si `m= 0, dans ce cas on ne fait pas

grand chose mais le coût n’est pas nul). On peut majorer cette complexité par O(1 + `m`s).

Remarque : Une remarque pour terminer. En Python, il est tout à fait possible de calculer la somme des éléments d’une liste, faire une recherche dans une liste, trier une liste, chercher un motif... à l’aide des commandes suivantes. Il faut avoir à l’esprit que ce ne sont pas opérations élémentaires, et ne pas oublier qu’elles cachent un travail important pour le processeur5!

sum(L) calcul de la somme des éléments de la listeL. max(L), min(L) calcul du max/min de la liste L

L.index(x) calcul du premier indicei tel que L[i] est égal à x s’il existe, produit une erreur sinon. x**n calcul de la puissance par exponentiation rapide si x est un entier et n un entier positif. x in L recherche dex dans L.

L.sort() tri de la listeL, avec un tri plus efficace que le tri par sélection !

m in s recherche dem comme motif de s (avec un algorithme plus efficace que celui ci-dessus).

5. La commande x in L est une recherche linéaire dans la liste. Le lecteur intéressé pourra vérifier que notre algorithme de recherche dichotomique est bien plus efficace sur une liste triée que l’instruction Python x in L, sur des listes assez grosses (j’obtiens un facteur 10000 dans le temps d’exécution sur une liste de taille 107). Il ne faut pas croire qu’une instruction d’une ligne s’exécute rapidement !

3.4. RECHERCHE D’UN MOTIF DANS UNE CHAÎNE DE CARACTÈRES Lycée Masséna

On peut utiliser également une fonction Python pour la recherche dichotomique, avec une légère modification d’une fonction importée du modulebisect.

Algorithme de recherche dichotomique (Python) import bisect

def cherche_dicho_Python(L,x): i=bisect.bisect_left(L,x) return i<len(L) and L[i]==x

Deuxième partie

Chapitre 4

Estimation d’erreurs numériques

Puisque les réels ne sont représentés en machine que sous la forme de flottants, ils ne sont connus que de manière approchée. De plus, la somme ou le produit de deux flottants est également approchée. On va voir dans ce chapitre comment les erreurs se propagent, et on étudiera par l’exemple les phénomènes d’instabilité numérique, en essayant de voir comment limiter les effets néfastes de l’approximation.

4.1

Rappels sur la représentation des nombres réels

On rappelle qu’un flottant normalisé se représente avec m bits de mantisse sous la forme x = (−1)s× 1.M1· · · Mm

2

× 2E

où les Mi ∈ {0, 1} sont les bits de la mantisse, stockés de manière contigue. L’exposant (décalé) et le signe sont

également représentés en interne, contrairement au 1 précédant la mantisse. La finitude de m (nombre de bits de la mantisse) implique que les réels ne sont représentés que de manière approchée. Pour x un réel non nul qui n’est ni trop grand, ni trop petit en valeur absolue (pour que l’exposant associé à son développement en base 2 tienne sur le nombre de bits alloués), en considérant ˜x le flottant le plus proche, on s’aperçoit que

x − ˜x x ≤ 2 E−m 2E = 2 −m

Ce nombre est la précision relative donnée par les m bits de mantisse, indépendante du réel représenté. Rappelons que sur 64 bits (ce qui est aujourd’hui le nombre de bits usuellement alloués à la représentation d’un flottant), m a pour valeur 52. Ainsi la précision relative est de 2−52' 2.22 × 10−16.

Dans la suite, on notera ε cette précision relative.