Académique Documents
Professionnel Documents
Culture Documents
Informatique
Table des matières
1 Structures linéaires, opérations, complexité 1
1.1 Premiers éléments sur la complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Structures linéaires en informatique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Structures linéaires en Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Conversions de types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2 Effets de bord 10
7 Complexité algorithmique 30
7.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
7.2 Classification de la complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7.3 Exemples de calculs de complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
8 Recursivité 34
8.1 Généralités sur la récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
8.2 Différents types de récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
8.3 Exemple emblématique : Les tours de Hanoï . . . . . . . . . . . . . . . . . . . . . . . . . 38
9 Graphes 41
9.1 Introduction et vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
9.2 Matrice d’adjacence et liste d’adjacence d’un graphe . . . . . . . . . . . . . . . . . . . . 43
9.3 Parcours d’un graphe et plus court chemin . . . . . . . . . . . . . . . . . . . . . . . . . . 46
9.4 Deux applications au parcours d’un graphe . . . . . . . . . . . . . . . . . . . . . . . . . 50
9.5 Parcours d’un graphe pondéré et plus court chemin . . . . . . . . . . . . . . . . . . . . . 51
Vidal AGNIEL
Aidé par Jean-Christophe FAUVEAU
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
On note f (n) = O(g(n)) s’il existe α > 0 tel que f (n) ⩽ αg(n) pour tout n ≥ 0.
On dit alors que f est un "grand O" de g, ou que f est dominée par g.
Par exemple, on a 5n2 + 200.n = O(n2 ).
On considère que les complexités “raisonnables” pour un algorithme sont les suivantes :
1. complexité logarithmique en O(log(n)) ;
2. complexité linéaire en O(n) ;
3. complexité semi-linéaire en O(n log(n)) ;
4. complexité quadratique en O(n2 )
Au delà, les temps de calcul aumetent trop vite en fonction de n.
1
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Les tableaux
Un tableau (array) est une suite de variables de même type associées à des emplacements consécutifs
de la mémoire :
1. Un tableau est une structure de donnée statique (sa taille ne change pas).
2. Un tableau est une structure de donnée mutable (on peut changer son contenu).
3. Les éléments du tableau sont accessibles en lecture et en écriture en temps constant O(1).
Les listes
Une liste contient un nombre fini de données, qui sont rangées dans un certain ordre. Ces données ne
sont pas stockées directement dans la liste, elles se trouvent à divers emplacements dans la mémoire du
système. Pour chaque case de la liste, la liste associe un pointeur indiquant la localisation (l’adresse)
dans la mémoire de la donnée concernée.
1. Une liste est une structure de donnée dynamique et mutable (on peut modifier sa taille et son
contenu).
2. Le n-ième élément d’une liste est accessible avec une complexité en O(n).
Un certain nombre de structures linéaires s’écrit et s’utilise en Python de façon très similaire à une liste
(piles et files par exemple). La machine fait par contre très bien la différence, grâce au type de chaque
objet. Il faut ainsi faire attention dans ses algorithmes à choisir le bon type d’objet selon les opérations
que l’on veut faire/informations que l’on veut stocker.
2
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Les dictionnaires
Un dictionnaire est très semblable à une liste. Il stocke un nombre fini de données, qui sont rangées.
Pour chaque donnée il associe un pointeur indiquant la localisation (l’adresse) dans la mémoire de la
donnée.
Cependant, au lieu de référencer chacune de ses données avec un nombre entre 0 et n − 1, un diction-
naire utilise des clés. Les clés sont des mots qui servent à indexer les éléments, comme les mots d’un
dictionnaire.
Les piles
Il s’agit d’une structure de données qui repose sur le principe :
Dernier Entré, Premier Sorti : Last In, First Out (ou LIFO)
Un peu comme une pile d’assiettes, c’est la dernière assiette de la pile qui est utilisée en premier.
Les files
Le principe de cette structure de donnée abstraite est le suivant :
Premier Entré, Premier Sorti : Fist In, First Out (ou FIFO)
Un peu comme une file d’attente devant le bus, c’est la première personne de la file qui monte en premier.
Les piles et les files sont des sortes de listes pour lesquelles certaines opérations spécifiques sont optimisées
(suppression du premier/dernier élément).
3
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
• Le premier élément d’une liste est le numéro 0, et le n-ème le numéro n − 1. Le premier terme
d’une liste L de n élément est appelé par L[0], et le dernier par L[n-1].
• La commande L1 = L[i:j] crée une nouvelle liste (appelée "tranche") composée des éléments
[L[i],...,L[j-1]] (de i à j − 1). C’est le slicing.
• Ajout d’éléments : L.append(x) (ajoute x à la fin de la liste), L.insert(i,x) (ajoute x à la
position i).
• Concaténation de deux listes : L.extend(L1), L + L1.
Il y a une différence subtile entre cex deux commandes.
• Indexation : L.index(’a’) renvoie l’indice de la première apparition de ’a’, sinon une exception
est déclenchée.
• Pour tester l’existence d’un élément : x in L (renvoie True ou False).
• Supprimer un élément : L.pop() retire le dernier élément de la liste.
L.remove(x) retire la première occurence de x rencontrée dans L.
Les espaces libres sont alloués au fur et à mesure que la liste s’agrandit, en O(1) tant qu’il reste de
l’espace.
. . . S’il n’y a plus d’espace libre, la liste est redimensionnée en lui allouant un espace plus grand (selon
une formule explicite).
Un nouveau pointeur est ensuite créé et les pointeurs existants modifiés, ce qui est réalisé en O(n).
4
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
La suppression d’un élément quelconque de la liste est réalisée en O(n) (il faut modifier les pointeurs).
Finalement, lorsque la liste devient trop petite on libère une partie de l’espace alloué.
La classe list de Python tente de tirer le meilleur parti de la mémoire en utilisant des tableaux et en
modifiant la taille des tableaux lorsque cela devient nécessaire.
5
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Exercice 1. Ecrire une fonction total_index prenant en entrée une liste L, un élément x, et renvoyant
la liste (éventuellement vide) des positions de x dans L.
Remarque 5.
• Les éléments d’un tuple ont un ordre défini, tout comme ceux d’une liste.
• Le slicing fonctionne aussi. Lorsque vous découpez un tuple, vous obtenez un nouveau tuple.
• Vous ne pouvez pas modifier un élément d’un tuple.
Tester : a=1; t=(a,1,a); t puis a=2; t;
• Vous ne pouvez pas ajouter d’élément à un tuple. Les tuples n’ont pas de méthode append ou
extend.
• Vous ne pouvez pas enlever d’éléments d’un tuple. Ils n’ont pas de méthode remove ou pop.
• Vous pouvez toutefois utiliser in pour vérifier l’existence d’un élément dans un tuple.
Remarque 6.
• Les tuple sont plus rapides d’accès que les listes, car leurs données sont statiques.
• Votre code est plus sûr si vous "protégez en écriture" les données qui n’ont pas besoin d’être
modifiées.
Tester avec la suite d’instructions :
> > > a=[1,2]; t=(2,"toto",a); t; a[1]=0; t
• On peut obtenir un tuple t à partir d’une liste L avec t=tuple(L). On peut obtenir une liste L
à partir d’un tuple t avec L=list(t).
Exemple 8.
> > > C="Chaine f(x)=25y."
> > > D="avec des ’ ’, ca marche !"
> > > print(D)
avec des ’ ’, ca marche !
Les chaînes de caractères servent principalement pour l’affichage de texte (résultat, boîte de dialogue,
sélection de choix,. . .).
Elles peuvent aussi gérer les caractères spéciaux (saut de ligne \n, indentation,. . .), qui prennent effet
avec la fonction print. Il faut pour cela utiliser des triple guillements
Exemple 9.
> > > Ch="""C’est facile les chaînes de caractère\nil suffit de bien choisir la méthode...""".
Comparer > > > Ch et > > > print(Ch)
6
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
7
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
1.3.7 Piles
Sur Python, il n’y a pas besoin de définir un nouvel objet pour manipuler des piles : les listes Python
(list) possèdent déjà toutes les opérations fondamentales nécessaires : l.append(x) (ajouter un nouvel
élément) et l.pop() (supprimer le dernier élément).
Le temps de calcul de ces deux opérations est déjà très optimisé, ce qui est ce que l’on recherche quand
on veut manipuler une pile.
Exemple 16.
L=[1,3,5]
type(L)
L.pop()
L.append("chat")
L
1.3.8 Files
Sur Python, on peut utiliser une liste comme une file, avec l.append(x) (ajouter un nouvel élément)
et l.remove(L[0]) (retirer le premier élément).
Mais la fonction remove nécessite trop de temps de calcul (sa complexité est en O(n)). Ce n’est donc
pas efficace.
Les files (queue) sur Python sont utilisables avec le module queue. (import queue as *).
Leurs opérations sont L.append(x) (ajouter un nouvel élément) et L.popleft() (retirer le premier
élément) sont optimisées (complexité en O(1)).
Cependant, on prefère importer les files à double-extrémité (double-ended queues), qui sont encore
plus optimisées.
Cet objet peut servir à la fois de pile et de file (from collections import deque) avec les opérations
L.pop(), L.popleft(), L.append(), L.appendleft(). L’ajout et le retrait d’un élément au début
ou à la fin d’un deque ont une complexité en O(1).
Exemple 17.
from collections import deque
L=deque([1,2,"mer"])
type(L)
L.popleft()
L.appendleft(-2)
L.pop()
L.append("chat")
L
8
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Pour les listes/piles/files/tuple, la conversion fait sens. Pour les nombres, les nombres entiers peuvent
être convertis en nombre flottants (réels). La conversion flottant → entier est possible, mais pas toujours
exacte.
Une chaîne de caractères qui ne contient que des chiffres peut être convertie en nombres (sinon non).
Exemple 18.
> > > chaine=’123’
> > > int(chaine)+3
126
> > > float(chaine)+3
126.0
> > > str(23+12)
’35’
Exemple 19.
>>> K=([1,2],"chat",5)
>>> L=list(K)
>>> L
[[1, 2], ’chat’, 5]
9
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Effets de bord
Définition 20.
Une opération informatique est à effet de bord si elle modifie le contenu des données en entrée.
Un algorithme est à effet de bord si elle modifie le contenu des données en entrée.
En mathématiques, avec une fonction f et un nombre x, on conserve toujours une distinction entre la
valeur en entrée x et son image f (x). On garde alors l’information de ces deux éléments.
En informatique, si une opération f a une effet de bord sur une variable x, lorsque l’on exécute f la
variable x change. La machine ne conservera pas en mémoire l’ancienne valeur de x, il n’y a pas de
retour en arrière possible (sauf si on anticipe cela en amont).
Les effets de bord sont intéressants en informatique quant on cherche à modifier x pour l’adapter à une
situation donnée.
Si l’on veut au contraire conserver la valeur initiale de x dans un algorithme, il faut faire attention à ne
pas introduire d’effet de bord.
De par leur structure, les listes, tableaux, piles, files, . . . sont souvent sensibles aux effets de bord. Cela
est moins le cas pour les nombres (entiers, flottants).
Exemple 21.
• Pour une liste L, l’opération L.pop() (supprimer le dernier élément) est à effet de bord. La
machine modifie la liste L et renvoie ce résultat.
Une fois le dernier élément supprimé, il n’y a pas d’opération pour revenir en arrière.
• L’opération L.append(x) est aussi à effet de bord.
Comme on ajoute une nouvelle information, il est possible de revenir en arrière avec L.pop().
• Une opération de slicing L’=L[i:j] (tranche entre i et j − 1) crée une nouvelle liste. Elle n’est
pas à effet de bord.
• Pour un algorithme de tri, on cherche à avoir un effet de bord : modifier la liste L de façon à ce
qu’elle soit plus pratique à utiliser. Il n’y a pas d’intérêt à conserver la liste non-triée de départ.
Si l’on ne veut pas avoir d’effet de bord dans un algorithme, une méthode efficace est de créer une copie
des données initiales. On peut alors modifier les copies tout en conservant les valeurs initiales.
Exemple 22. Pour créer une copie d’un nombre n en Python, il suffit de définir une nouvelle variable
n1 et d’indiquer qu’elle prend la même valeur que n.
>>> n=2
>>> n1=n
>>> n1=3
>>> n
2
Pour créer une copie d’une liste L en Python, il faut utiliser le slicing (L2=L[:]).
>>> L=[1,1,2]
>>> L1=L
>>> L2=L[:]
>>> L1[0]=3
>>> L
[3, 1, 2]
>>> L2
[1, 1, 2]
10
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
3.1 Définition
3.1.1 Contexte
Avant de tester un algorithme, sur des données éventuellement de grande taille et avec éventuellement
beaucoup d’opérations, on veut s’assurer que cet algorithme s’arrêtera en un nombre fini d’étapes.
Définition 23. La terminaison est le fait de prouver théoriquement qu’un algorithme donné s’arrêtera
toujours.
Cet élément est important car il existe des algorithmes pour lesquels on n’a toujours pas la réponse,
comme par exemple l’algorithme associé à la "convergence" dans le calcul de la suite de Syracuse.
Algorithme 1 : fonction syracuse(n)
Entrées : un entier n
Syr ← n;
tant que Syr ̸= 1 faire
si Syr est pair alors
Syr ← Syr/2
sinon
Syr ← 3 ∗ Syr + 1
fin
fin
Remarque 24. Pour les instructions informatiques, les affectations/copies/tests logiques terminent
tous. Les boucles for aussi.
Le problème de la terminaison réside dans les boucles (boucles while et appels récursifs), qui peuvent
éventuellement ne jamais se terminer.
Définition 26. Une fonction de terminaison (ou variant de boucle) est une fonction f , qui dépend
des variables dans la boucle, à valeurs entières positives, qui décroît strictement à chaque passage dans
la boucle (ou à chaque appel récursif).
Comme cette fonction f ne peut prendre qu’un nombre fini de valeurs, la boucle ne peut pas être
exécutée autant de fois que l’on veut.
Plus précisément, si n0 est le plus grand entier atteint par la fonction f , l’image de la fonction f contient
au plus n0 + 1 valeurs, et donc la boucle ne peut pas être exécutée plus de n0 + 1 fois.
Méthode 27 (Démontrer la terminaison d’une boucle).
Pour une boucle (while ou appel récursif) donnée à l’intérieur d’un algorithme, on cherche f une fonction
de terminaison de la boucle, en fonction des quantités qui varient à chaque passage de la boucle.
Une fois que l’on a exhibé f , on montre qu’elle est positive et strictement décroissante à chaque passage
11
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
de la boucle.
On conclut ensuite que la boucle se terminera toujours en un nombre fini d’étapes. La terminaison est
vérifiée.
La fonction de terminaison dépend toujours des variables qui apparaissent dans la condition while
(. . .):, ou dans les paramètres de l’appel récursif (nous y reviendrons plus tard).
Exemple 28 (Recherche par dichotomie dans une liste triée).
Soit L une liste triée à n éléments, et a ∈ R. On veut savoir si a est dans la liste L ou non.
Algorithme 2 : fonction RechDicho(L,a)
Entrées : L[0..n − 1] une liste triée et a un nombre
Sorties : un booléen
1 g ← 0 ; d ← n − 1;
2 tant que d − g > 0 faire
3 m ← Ent((d + g)/2);
4 si L[m] < a alors
5 g ←m+1
6 sinon
7 d←m
8 fin
9 fin
10 si L[g]==a alors
11 retourner Vrai
12 sinon
13 retourner Faux
14 fin
Pour que la boucle while se termine, il faut que la condition d − g > 0 (ligne 2) soit contredite.
Étudions les variations de la quantité d − g
En fonction du test (L[m] < a), soit g devient m + 1, soit d devient m.
Notons (gn , dn , mn ) les valeurs de g et de d après n passages dans la boucle.
On a donc dn+1 − gn+1 = dn − (mn + 1) ou dn+1 − gn+1 =n m − gn .
La partie entière nous donne l’encadrement dn +g 2
n
≤ mn < dn +g2
n
+ 1.
dn −gn
Cela donne les encadrements : 2 − 1 < dn − (mn + 1) ≤ 2 et dn −g dn −gn
2
n
− 1 ≤ mn − gn < dn −g
2
n
.
dn −gn
Dans tous les cas, on a la majoration : dn+1 − gn+1 ≤ 2 .
Comme on a dn − gn > 0, on en déduit que dn+1 − gn+1 ≤ dn −g 2
n
< dn − gn .
Ainsi, on pose la fonction f (g, d) = g − d.
Cette fonction est à valeurs entières.
Cette fonction est positive lors de chaque passage dans la boucle while (elle devient négative quand la
boucle se termine).
A chaque passage dans la boucle, cette fonction est strictement décroissante.
La fonction f est donc une fonction de terminaison de cette boucle while. Cette boucle se termine donc
après un nombre fini d’exécutions.
12
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
def mystere(n) :
L=[]
while n >= 0 :
L.append(n % 10)
n = n//10
return L
def factoriel(n) :
if n == 0 :
return 1
else :
return n*factoriel(n-1)
Cet algorithme utilise les propriéés 0! = 1 et n! = n(n − 1)! pour calculer n! de façon récursive.
A chaque appel récursif, le paramètre n change. Après k appels récursifs, notons nk la valeur de la
variable n. On a alors la relation nk+1 = nk − 1.
On pose la fonction f (n) = n. Il n’est pas nécessaire ici de montrer que f est une fonction de terminai-
son, on a mieux.
La suite (nk )k est une suite arithmétique de raison 1 avec n0 = n. Elle s’écrit donc nk = n − k.
Au bout de n appels récursifs, quand k = n, on a nn = n − n = 0.
Or, quand l’entier en entrée vaut 0, l’algorithme facoriel se termine.
Donc, il ne peut pas y avoir plus de n appels récursifs.
Ainsi, l’algorithme factoriel termine toujours en un nombre fini d’étapes.
def M91(n) :
assert (type(n)==int) and (n>=0)
if n>100 :
return n-10
else :
13
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
return M91(M91(n+11))
14
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
15
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Exemples d’invariants
Exemple 38.
def pow(x,n):
z = 1
m = n
y = x
while m > 0:
if m % 2 == 1:
z = z * y
m = m // 2
y := y * y
return(z)
• Montrons que la proposition (xn = ym *z) est un invariant de boucle.
Soit k le nombre de passages dans la boucle, et yk , zk , mk les valeurs de y, z, m lors du k-ème pas-
sage.
Pour k = 0 (entrée de la boucle), on a y0 = x, m0 = n, z0 = 1. On obtient bien y0m0 .z0 = xn .
Soit k ≥ 0. On regarde yk , zk , mk .
2mk
m
Si mk est pair, on a zk+1 = zk , mk+1 = m2k et yk+1 = yk2 . Cela donne alors yk+1
k+1
.zk+1 = yk 2 zk =
mk
yk zk .
mk+1
Si mk est impair, on a zk+1 = zk .yk , mk+1 = mk2−1 et yk+1 = yk2 . Cela donne alors yk+1 .zk+1 =
mk −1
2
yk 2 zk .yk = ykmk zk .
mk+1
Ainsi, dans tous les cas, on a la relation de récurrence yk+1 .zk+1 = ykmk zk .
mk mk
Ainsi, la suite (yk .zk )k est constante. On a donc yk .zk = y0m0 .z0 = xn pour tout k ≥ 0.
Cela démontre que la proposition (xn = ym *z) est un invariant de boucle.
La boucle "while m>0" se termine lorsque m ≤ 0. On a initialement m = n, qui est un entier po-
sitif. L’opération m=m//2 fourit encore un entier positif, comme quotient de division euclidienne.
Ainsi, cette boucle se termine quand m = 0. (La condition (m ≥ 0) est aussi un invariant de boucle)
A la fin de la boucle, l’invariant de boucle nous donne : xn = y m .z = y 0 .z = z.
On obtient ainsi que la valeur finale de la variable z est z = xn . Cette valeur est celle attendue.
L’algorithme pow est donc correct.
16
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Exemple 39.
Algorithme 7 : Algorithme Somme Arithmétique
Entrées : un entier n
Sorties : la somme des n premiers entiers
i ← 0;
somme ← 0;
tant que i < n faire
i ← i + 1;
somme ← somme + i;
fin
retourner somme
Dans cet algorithme, on regarde les opérations effectuées dans la boucle while. Soit k le nombre de pas-
sages dans la boucle. On peut remarquer que la variable somme (qui dépend de k) vérifie : (sommek =
1 + · · · + k).
Cette condition est un invariant de boucle. Montrons-le par récurrence sur k ≥ 0.
Initialisation : A l’entrée de la boucle on a somme0 = 0.
Hérédité : Supposons la proposition vraie pour un entier k ≥ 0. On a alors sommek+1 = sommek +ik+1
et ik+1 = ik + 1.
La suite (ik )k est une suite arithmétique de raison 1, de premier terme i0 = 0. On a donc ik = k.
On a donc sommek+1 = sommek + (k + 1) = 1 + . . . + k + (k + 1).
Cela termine la récurrence. Cela démontre que (sommek = 1 + · · · + k) est un invariant de boucle.
De plus, la boucle while se termine au bout de n étapes exactement, avec in = n et car ik = k < n
quand k < n.
Pn
Ainsi, quand en sortie de boucle, on a somme = 1 + . . . + n = k=1 k = n(n+1)
2 .
Cela démontre que cet algorithme est correct.
Exemple 40. Comment montrer rapidement que 13574 est divisible par 9 ?
Soit un nombre x écrit sous forme décimale x = an an−1 . . . a0 (10) .
Alors x est divisible par 9 ssi la somme de ses chiffres est un multiple de 9.
Exercice : Démontrer ce résultat.
On construit alors les algorithmes suivants : def mod9(n):
a=str(abs(n))
r=int(a[0])
for k in range(1,len(a)):
r=r+int(a[k])
return(abs(r))
Le premier algorithme récupère les chiffres de n sous forme de chaîne de caractères, et les ajoute.
def preuve9(n):
m=abs(n)
while m > 9:
m=mod9(m)
if m==0:
return("le nombre est divisible par 9")
else:
return("le nombre n’est pas divisible par 9")
Le second algorithme applique l’algorithme mod9 à m tant que m est strictement supérieur à 9.
Premièrement, l’algorithme mod9 se termine bien, car il ne contient qu’une boucle for.
Pk
Un invariant de la boucle de mod9 est la proposition (rk = i=0 a[i]), où k est le nombre de passages
dans la boucle.
On a en effet r0 = a0 et rk+1 = rk + ak . Ces propriétés permettent de démontrer par récurrence sur k
Pk
que (rk = i=0 a[i]) est vraie tout au long de la boucle.
Plen(a)
Lorsque l’on sort de la boucle on a k = len(a). On obtient ainsi rk = i=0 a[i], qui est la somme de
tous les chiffres de n.
L’algorithme mod9 est donc correct.
17
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Pour la boucle while de l’algorithme preuve9, une fonction de terminaison est f (m) = m. Posons k
le nombre de pasages dans la boucle. En effet, mk est toujours un entier positif car l’algorithme mod9
renvoie un entier positif. On a la relation mk+1 = mod9(mk ).
De plus, si m > 9 on a m ≥ 10.
Pour m = a0 + 10.a1 + 102 .a2 + . . . + 10r .ar , avec ar ̸= 0, on a mod9(m) = a0 + a1 + a2 + . . . + ar < m.
On obtient donc que tant que m > 9, on a mk+1 < mk . La fonction f est ainsi strictement décroissante
à chaque exécution de la boucle.
Cette fonction est bien une fonction de terminaison de la boucle. Cette boucle se termine.
Un invariant de la boucle de preuve9 est la divisibilité de mk par 9 (la proposition "9 | n ⇔ 9 | mk ").
En effet, on a 9|mod9(mk ) ⇐⇒ 9|mk .
Cela permet de montrer par récurrence sur k que 9 | n ⇔ 9 | m0 ⇔ 9 | mk .
Enfin, lorsque la boucle se termine, au bout de r étapes, on a mr < 9. Comme mr est un entier positif,
il est compris entre 0 et 8.
Le seul entier dans {0, . . . , 8} qui est divisible par 9 est 0.
On a donc que 9 | n ⇔ 9 | mr ⇔ mr = 0.
Cela démontre que l’algorithme preuve9 est correct.
Dans les faits, il faudra surtout trouver une fonction de terminaison f , un invariant de boucle P , ainsi
que les éléments (valeurs initiales, relations de récurrence) qui permettent de prouver que f est bien un
invariant de boucle et que P est bien une fonction de terminaison.
On ne vous demandera pas de rédiger des récurrences en permanence.
Il n’y a pas de technique générique de recherche d’invariant. Il faut regarder ce qu’il se passe lors des
premiers passages de la boucle pour chaque variable et il faut chercher une quantité qui reste fixe (ou
une égalité qui reste vraie) par rapport aux opérations effectuées/au résultat voulu. Ensuite, on corrige
éventuelement la proposition P pour obtenir un invariant correct qu’il sera possible de démontrer avec
une relation de récurrence.
En général, un invariant de boucle consiste en la description exacte et précise des liens entre les arguments
présents dans la boucle.
18
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
19
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
• Toutes les données que l’on stocke en informatique sont sous forme booléenne (un assemblage
d’éléments à deux états : 0 ou 1). On parle de représentation binaire.
• Ces données sont de nature multiple : nombres, instructions, textes, images, sons, mais elles sont
toujours représentées en binaire.
• Pour traiter cette information, on utilise des transistors.
Un transistor fonctionne avec une logique à deux états, grâce à un courant électrique : 0 le courant
ne passe pas, 1 le courant passe.
• Les systèmes informatiques actuels sont composés circuits intégrés rassemblant des centaines de
millions de transistors.
Pour traiter de l’information en binaire, on va souvent chercher à la découper en blocs de taille fixée.
Les 3 tailles les plus utilisées sont :
• Le bit. C’est une information qui vaut 0 ou 1 (contraction de Binary Digit).
• L’octet (byte). Un octet (ou byte) est constitué de 8 bits.
• Le mot (word).
Suivant le type de processeur, les mots peuvent avoir 16, 32, 64, 128 bits ou plus. (toujours une
puissance de 2).
Remarque 47. Attention à ne pas confondre en anglais byte et bit.
On peut facilement se tromper sur les capacités de stockage ou sur les vitesses de transfert. Par exemple
entre Mégabyte (Mégaoctet) et Mégabit (Mégabit) ou entre kilobyte/s (kilooctet/s) et kilobit/s (kilobit/s).
Un mégabyte c’est un mégaoctet, soit 1000 octets, donc 8000 bits.
n = ak B k + ak−1 B k−1 + . . . + a1 B + a0 .
n = (ak ak−1 . . . a1 a0 )B
| {z }
k+1 chif f res
Le membre de droite représente n dans la numérotation à position en base B. Chaque ai est un chiffre
de n dans son écriture en base B.
Pour la base 16, on prend comme chiffres ai ∈ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F }.
20
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Proposition 50 (Décomposition en base B). Pour décomposer un entier positif n en base B, on utilise
des divisions euclidiennes successives par B.
On divise les diviseurs, et les restes fournissent les chiffres de l’écriture en base B.
Algorithme 10 : Représentation en base B
Entrées : n et B deux entiers strictement positifs, avec B > 1
Sorties : la représentation (ai . . . a1 a0 )B
i←0
tant que n ̸= 0 faire
ai ← resteDivEucl(n, B);
n ← quoDivEucl(n, B);
i ← i + 1;
fin
retourner (ai . . . a0 )B
Si l’on connaît les chiffres de n en base B, on peut retrouver la valeur de n en base 10 à l’aide de k
multiplications et d’au plus k additions.
Cela évite aussi de stocker en mémoire les valeurs de B 2 , B 3 , B 4 , . . . , B k , qu’on utilise lorsque l’on fait
un calcul en base 10 à la main.
Exercice 7. Ecrire une fonction Python correspondant à cet algorithme. Entrée : Une liste (ak , . . . , a0 )
d’entiers et B > 0. Sortie : Valeur de (ak . . . a0 )B en base 10.
Les nombres écrits en binaire sont très difficilement lisibles pour nous, car ils ont trop peu de chiffres.
L’écriture hexadécimale apporte dans certaines situations davantage de lisibilité. On cherche souvent à
représenter des quantités (couleurs RGB, pointeurs, adresses) sous forme hexadécimale.
Le fait que 16 = 24 permet une conversion assez pratique.
Remarque 54 (Conversion binaire - hexadécimal).
Pour convertir une écriture en binaire en hexadécimal, on regroupe les chiffres par paquet de 4 (ou moins
pour le dernier paquet), et on convertit chaque paquet en hexadécimal.
Par exemple, 1100110112 = 1 1001 00112 = 19B16 . (car 10012 = 9 et 10112 = 11 = B16 )
Les nombres écrits sur 4 bits en binaire correspondent aux entiers entre 0 et 15, donc aux chiffres 0 à
F en hexadécimal.
Exercice 8.
• Déterminer la représentation en base 10 de 101010102 .
• Déterminer la représentation en base 2 de 1, 3, 7, 15, 31. Quel est le prochain nombre de la
série ? Pourquoi ?
• Pour n ∈ N∗ fixé, combien d’entiers naturels peut-on représenter avec un mot de n bits ?
• Réciproquement, combien de bits faut il, au minimum, pour représenter tous les entiers dont
l’écriture décimale contient m chiffres ?
21
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Sortie = 1 ssi au moins une entrée vaut 1 Sortie = 1 ssi une unique entrée vaut 1
Sortie = 1 ssi au moins une entrée vaut 0 Sortie = 1 ssi les deux entrées valent 0
Additionneur
Ce montage permet d’additionner deux bits x et y, en renvoyant un bit de somme et un bit de retenue :
Additionneur 2-bits
On peut alors concevoir un motage pour additionner des nombres à 2 bits x1 x0 et y1 y0 , en donnant le
résultat sous forme d’un nombre à deux bits z1 z0 , et d’une retenue r1 .
22
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
On peut alors connecter des additionneurs en série sur le même principe de sorte à obtenir un addition-
neur entre deux nombres de n bits.
Remarque 55. On obtiendra en bout de chaîne un bit de retenue, qui influe sur le résultat.
1. Si le résultat ne reste pas dans l’intervalle d’entiers représentables (donné par le nombre de bits),
la retenue vaudra 1 et la représentation sera erronée. On parle alors d’overflow (dépassement par
excès).
Les nombres 10 = 10102 et 15 = 11112 s’écrivent avec 4 bits en binaire, mais 10 + 15 = 25 =
110012 nécessite 5 bits.
2. En revanche, si la retenue vaut 0, c’est qu’il n’y a pas de dépassement de capacité et la représen-
tation est correcte.
Exercice 9. L’opérateur xor ("ou exclusif", noté aussi ∧) compare deux nombres bit par bit.
Il renvoie 1 chaque fois que les deux bits correspondants sont différents et écrit 0 sinon.
Par exemple 4 xor 3 vaut 7 et 7 xor 2 vaut 5.
Calculer 10 xor 15 et 2A16 xor 4B16 .
Que valent a xor (b xor b) et (a xor b) xor b ?
entier représentation
0 0000 0000 0000 0000
1 0000 0000 0000 0001
62 0000 0000 0011 1110
32767 0111 1111 1111 1111
-1 1111 1111 1111 1111
-62 1111 1111 1100 0010
-32768 1000 0000 0000 0000
Exercice 10.
1. Pour n = 8 bits, en complément à 2, écrire 0 et −128.
2. Trouver les entiers relatifs dont les représentations en complément à 2 sur n = 8 bits sont 0001
0111 et 1000 1100.
23
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Remarque 57. Dans l’écriture en complément à 2, certains nombres ont des écritures facilement
identifiables, peu importe le nombre n de bits :
• 0 = (0000 . . . 0000)2
• 1 = (0000 . . . 0001)2
• −1 = (1111 . . . 1111)2
• 2n−1 − 1 = (0111 . . . 1111)2
• −2n−1 = (1000 . . . 0000)2
Et, les nombres négatifs sont ceux dont le bit le plus à gauche vaut 1. Ce bit est appelé le bit de signe.
Ainsi, l’entier signé (1111 0100)2 (sur 8 bits) est négatif.
Le résultat obtenu n’est pas n’importe quel nombre : il diffère du résultat par un multiple de 2n (on dit
qu’il est congru au résultat modulo 2n ).
Dans l’exemple, on a 310 = 44 + 256 = 44 + 28 , donc on a bien 44 ≡ 310[256].
Le dépassement n’est ainsi pas un problème si l’on veut étudier des résultats à un multiple de 2n près
(ou des restes de division euclidienne par 2n ). Ce domaine s’appelle le calcul modulaire.
24
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
25
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Un nombre décimal est en fait un nombre de la forme 10km avec k ∈ Z et m ∈ N. De même, un nombre
"décimal" en base 2 est en fait de la forme 2rs avec r ∈ Z et s ∈ N.
Ainsi, la majorité des nombres réels ne sont pas décimaux, ni "décimaux" en base 2, ( 32 par ex.). De
plus, des nombres décimaux comme 0, 2 = 15 ne sont pas "décimaux" en base 2.
On a par exemple : 32 = 0, 1010101010101010P . . .2
n
L’égalité est vraie au sens des limites : limn ( k=0, k impair ( 12 )k ) = 23 .
En ce sens, on a aussi : 0, 11111111 . . .2 = 1. (Pour x = (0, 11 . . .)2 on a 2x = 1 + x, donc x = 1. Preuve
identique à celle de 0, 99 . . . = 1 en base 10.)
Remarque 68. Pour stocker un nombre réel sous forme de flottant (sous forme décimale en base 2)
dans Python, on doit faire en général une approximation de ce nombre, et c’est cette approximation qui
est stockée.
Avec n bits, on ne peut stocker que 2n valeurs différentes. Pour une taille de stockage donnée il y a ainsi
une limite en taille pour les nombres flottants (à la fois pour de grands nombres et pour de très petits
nombres).
Définition 69 (Codage d’une partie fractionnaire en base 2). Soit x ∈ R, et y = x − ⌊x⌋ la partie
fractionnaire de x (y ∈ [0, 1[). Soit n ∈ N∗ . Pour approcher y en base 2 avec n bits :
1. On multiplie y par 2.
2. On regarde la partie entière de 2y, qui vaut 0 ou 1. On stocke cette valeur pour b1 .
3. On regarde la partie fractionnaire de 2y, càd y1 = 2y − ⌊2y⌋.
4. On regarde la partie entière de 2y1 , qui vaut 0 ou 1. On stocke cette valeur pour b2 .
5. On répète le procédé jusqu’à ce qu’il n’y ait plus de partie fractionnaire restante, ou que l’on ait
atteint un mot de n bits (0, b1 b2 . . . bn )2 .
Dans le premier cas, on a une valeur exacte de y. Dans le second on obtient une valeur approchée
à 2n près de y.
26
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Le signe est un bit (0 ou 1). La mantisse est une partie fractionnaire en base 2 à n bits. L’exposant est
un entier relatif en base 2 à m bits.
On le stocke sous la forme :
27
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Exercice 12. Exercice : Convertir +16, 5 en nombre flottant à 32 bits (simple précision).
On donnera le résultat final en binaire (32 bits) et en hexadécimal (8 caractères).
On obtient :
• Signe : 0 (positif)
• Exposant décalé : 10001001(2) = 137(10) . Donc l’exposant est 137 − (27 − 1) = 137 − 127 = 10.
• Mantisse : 11100111110000000000000.
• Finalement, on obtient le nombre x = (1, 11100111110000000000000)2 .210 =
(1110011111, 0000000000000)2 = ( 11110011111
| {z } , 0000000000000
| {z } )2 = 1951(10) .
partie entiere 1951 partie fractionnaire
28
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
• Le nombre flottant le plus grand que l’on peut obtenir est celui associé à (0 11 . . . 11 11 . . . 111),
n−1 n−1
c’est-à-dire +(1, 11 . . . 111)2 .22 ≃ 2.22 .
• Le nombre flottant le plus proche de 0 que l’on peut obtenir est celui associé à (0 00 . . . 00 00 . . . 000),
n−1
c’est-à-dire +(1, 00 . . . 00)2 .2−2 +1 = 22n−1 1
+1
.
• Avec les nombres flottants, on peut ainsi stocker à la fois des nombres relativement grands (de
n
l’ordre de 22 ) et des nombres relativements petits (de l’ordre de 221n ).
• Par convention les nombres (x 00 . . . 00 xx . . . xx) sont réservés pour 0 (exposant minimal), ceux
de la forme (0 11 . . . 11 xx . . . xx) sont réservés pour +∞ (positifs d’exposant maximal), et les
(1 11 . . . 11 xx . . . xx) sont réservés pour −∞ (négatifs d’exposant maximal).
29
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Complexité algorithmique
Table des matières du chapitre
7.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
7.1.1 Lien avec la taille des données . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
7.1.2 Différents types de complexité . . . . . . . . . . . . . . . . . . . . . . . . . . 31
7.1.3 Un exemple : la multiplication par exponentiation binaire . . . . . . . . . . . 32
7.2 Classification de la complexité . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7.3 Exemples de calculs de complexité . . . . . . . . . . . . . . . . . . . . . . . 33
7.1 Généralités
La complexité d’un algorithme représente “nombre d’opérations” nécessaire pour qu’il effectue son tra-
vail. Cette complexité donne une idée du temps nécessaire à l’algorithme pour qu’il soit exécuté par la
machine.
Cependant, toutes les opérations "élémentaires" ne prennent pas le même temps d’exécution.
Il y a plusieurs angles de vues concernant la complexité (nombre de tâches demandées à l’algorithme,
nombre d’opérations "élémentaires" effectuées, nombre d’opérations binaires effectuées par l’ordinateur).
Nous nous contenterons d’évaluer le nombre d’opérations élémentaires d’un certain type, qui caracté-
risent assez bien le temps d’exécution d’un algorithme.
Définition 79 (Opérations "élémentaires").
• Opérations arithmétiques : +, -, x, /, quotient //, reste % sur les entiers et flottants, . . .
• Comparaisons : <, <=, ==, !=, . . .
• Opérations logiques : not, and, or, xor.
• Affectation et lecture de variables : définir ou modifier une variable, append sur les listes,. . .
• Appel d’une fonction : appel à un autre algorithme, appel récursif.
Sur Python, ces opérations élémentaires sont codées de façon à optimiser au maximum le nombre
d’opérations binaires nécessaires (le temps de calcul). A moins d’étudier de l’informatique très théorique,
on fait confiance aux informaticiens qui ont passé du temps à optimiser les commandes les plus simples.
Dans un algorithme, on peut aussi s’intéresser à certaines opérations en particulier :
• Dans un algorithme de tri comparatif, une opération significative sera une comparaison.
• Dans un algorithme de manipulation de données, ce pourrait être le nombre d’accès à une donnée
générique de la structure.
• Dans une multiplication de polynômes à coefficients réels, ce sera le nombre de produits de réels.
• Dans le cas d’une fonction récursive, on cherchera à calculer le nombre d’appels récursifs.
• Dans un algorithme de traitement d’image, le nombre d’accès à un pixel et/ou le nombre d’opé-
rations d’affectation seront révélateurs du temps de calcul.
La complexité dépend souvent majoritairement de la taille de x. Pour le tri d’une liste, plus une liste
est grande et plus il y aura d’opérations à faire pour la trier.
Mais pas que : si la liste x est déjà presque triée, il faudra peu d’opérations pour terminer le tri. Pour
x une donnée de taille fixée, il se peut que la forme de x nécessite bien plus/bien moins d’opérations
qu’en moyenne.
La façon de stocker les données importe aussi. A part quelques cas particuliers (graphes) on ne cherchera
pas à étudier la complexité par rapport à la façon dont sont codées les données.
Remarque 80 (Quelques grandeurs pour les tailles de données). Pour les ordres de grandeurs, tout
comme pour le O(g(n)), on s’intéresse aux fonctions à une constante multiplicative près.
30
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Mais le graphe sera de taille M 2 si on choisit de coder G par une matrice A de taille M × M , dont le
coefficient ai,j vaut 1 s’il y a une arête du sommet i vers le sommet j, et 0 sinon.
Etudier un graphe via un tableau de listes demandera bien moins d’opérations que via une matrice,
même si la matrice en question contient beaucoup de zéros.
On cherchera d’abord à manipuler correctement les graphes avant de s’intéresser à ces notions plus
difficiles.
Remarque 83. Les calculs de complexité sont généralement effectués en ordre de grandeur, parfois
seulement de façon plus exacte.
2
Un résultat du type C(n) = O(n ln n) sera aussi utile que C(n) = (n + 23 ) ln( 3n 2n+4
+7n−1
).
Pour A, B deux matrices de Mn (R), effectuer le produit A × B revient à déterminer n2 nouveaux coef-
ficients. Pn
Avec la relation (AB)ij = k=1 ai,k bk,j , chaque coefficient nécessite n produits et n − 1 sommes.
On a donc au total n3 produits et n3 − n2 sommes à effectuer, soit 2n3 − n2 opérations. La complexité de
ce produit de matrices est de l’ordre de 2n3 . Il faut donc O(n3 ) opérations élémentaires pour un produit
de matrices.
31
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
32
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Remarque 84. On peut remarquer qu’il faut éviter les complexités supérieures à O(n2 ).
• Lorsque l’algorithme s’y prête, on contourne parfois la contrainte grâce à un parallélisme massif.
Le MilkyWay-2, accueille 3 120 000 coeurs pour une puissance développée de 33,86 petaflop/s.
Un PC puissant atteint aujourd’hui 40 gigaflops, soit la puissance de calcul des meilleurs super-
calculateurs en 1987.
• Les ordinateurs quantiques ont théoriquement une puissance de calcul multipliée par 2 par l’ajout
de 1 q-bit. . . Une machine à 300 q-bits pourrait rendre compte de l’évolution de l’univers depuis
le Big Bang (très discutable sur le plan réaliste).
def mini(L):
m = L[0]
for i in range(len(L)):
if m > L[i]:
m = L[i]
return(m)
Les opérations élémentaires importantes pour la complexité de cet algorithme sont le test et l’affectation.
Le nombre de tests est constant et vaut n = len(L).
Dans cet algorithme on a entre 0 et n affectations. Le nombre total d’opérations élémentaires est donc
de n dans le meilleur cas, et de 2n dans le pire cas. La complexité, en nombre d’opérations, de cet
algorithme est donc en O(n).
Exemple 86 (Le tri à bulles). Soit L1 un tableau à n éléments, que l’on veut trier.
def tribulle(L1):
L = L1[:]
for i in range(len(L)-1,-1,-1):
for j in range(i):
if L[j] > L[j+1]:
L[j],L[j+1] = L[j+1],L[j]
return(L)
Les opérations élémentaires que l’on considère pour la complexité de cet algorithme sont d’une part le
nombre de tests et d’autre part le nombre d’échanges.
Pn−1 Pi
Dans tous les cas, le nombre de tests vaut exactement i=1 j=1 1 = n(n−1) 2 .
Le nombre d’échanges dans le meilleur des cas est 0. Dans le pire cas, on a autant d’échanges que de
n(n − 1)
tests , soit .
2
Ainsi, la complexité de cet algorithme varie entre n(n−1)
2 et n(n − 1) = n2 − n.
Dans tous les cas, cette complexité est de l’ordre de n , c’est un O(n2 ).
2
33
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Recursivité
Table des matières du chapitre
8.1 Généralités sur la récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . 34
8.2 Différents types de récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . 36
8.2.1 La récursivité simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
8.2.2 La récursivité multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
8.2.3 La récursivité imbriquée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
8.2.4 La récursivité mutuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
8.3 Exemple emblématique : Les tours de Hanoï . . . . . . . . . . . . . . . . . 38
def factorielle(n) :
if n == 0 :
return 1
else :
return n*factorielle(n-1)
Pour n = 4, voici les appels récursifs effectués ainsi que la gestion de la pile d’exécution par Python :
34
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Il y a eu ainsi 4 appels récursifs, et il a fallu stocker 4 valeurs intermédiaires pour calculer 4!.
En général, cet algorithme est de complexité O(n) (n appels récursifs, n multiplications, n stockages de
valeurs intermédiaires).
Remarque 91. Les algorithmes récursifs ont des avantages :
1. Leurs expressions sont proches de la pensée mathématique.
2. Les algorithmes sont compacts et rapides à construire. Il faut savoir traiter des cas initiaux, et
pouvoir résoudre un cas général à partir d’une version de taille plus petite (avoir une sorte de
relation de récurrence).
Mais ils ont aussi des inconvénients.
1. Ils nécessitent davantage de calculs (complexié moins bonne) face à une version non-récursive.
2. Ils demandent davantage d’espace en mémoire pour stocker toutes les données en attente de
traitement (pile d’exécution, valeurs intermédiaires).
3. Il y a des limites sur le nombre d’appels récursifs, qui ne sont pas de très grands nombres. Par
exemple, Python limite à 1000 la profondeur d’appels d’une fonction récursive.
Cette limite d’appels récursifs peut être toutefois modifiée :
import sys
sys.setrecursionlimit (2000)
En général, la programmation explicite (impérative) est généralement plus efficace que la programmation
récursive. La programmation récursive n’est à envisager que si le problème s’y prête fortement. Par
exemple pour les tours de Hanoï.
Exemple 92 (Algorithme d’Euclide récursif).
Pour 0 < b < a, deux entiers, Euclide remarque que :
En itérant le processus, si a = qb + r, il vient que pgcd(a, b) = pgcd(b, r). Ceci fournit des valeurs
initiales et une relation de récurrence.
On peut alors constuire récursivement l’algorithme d’Euclide pour le calcul du pgcd.
def pgcd_explicite(a,b) :
while b != 0 :
a,b = b,a%b
return a
def pgcd_recursif(a,b) :
if b == 0 :
return a
35
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
return pgcd_recursif(b,a%b)
La version récursive fait apparaître clairement le fait que pgcd(a, b) = pgcd(b, r), ce qui n’est pas le cas
de la version explicite qui elle montre clairement les étapes de calcul de l’algorithme d’Euclide.
Il est intéressant de visualiser ces deux algorithmes sur des exemples.
Il existe différents types de récursivité, selon le nombre d’appels récursif et la façon dont on utilise
l’appel
Définition 94 (Récursivité terminale). On dit qu’un algorithme récursif est à récursivité terminale
s’il fait un seul appel récursif et que cet appel récursif ne nécessite pas de stocker des informations
intermédiaires.
Le résultat de la fonction appelée est immédiatement renvoyé par la fonction appelante.
L’algorithme récursif pour la fonction factorielle vu précédemment n’est pas à récursivité terminale car
il nécessite de stocker des informations intermédiaires avec les appels récursifs pour obtenir le résultat
final.
L’algorithme d’Euclide récursif est à récursivité terminale. Une fois que l’on obtient la valeur de pgcd(a, b)
dans le tout dernier appel récursif, il n’y a plus d’autres opérations à effectuer, la pile d’exécution se
vide et la valeur est simplement retournée comme résultat.
C’est pour cela que l’on parle de récursivité terminale.
def dicho(x, t) :
if len(t) == 0 :
return False
m = len(t) // 2
if x == t[m] :
return m
if x < t[m] :
return dicho(x, t[ :m])
else :
return dicho(x, t[m+1 :])
Cet algorithme est récursif. Chaque exécution de l’algorithme ne fait qu’un seul appel récursif au maxi-
mum (soit aucun, soit dicho(x, t[:m]), soit dicho(x, t[m+1:]).
De plus, les appels récursifs ne demandent aucune opération supplémentaire à part renvoyer la valeur
obtenue (qui sera la valeur obtenue par le dernier appel récursif).
Cet algorithme est donc à récursivité terminale.
36
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Cet algorithme se distingue du premier par l’usage d’un paramètre supplémentaire (accu). Avec ce para-
mètre, il n’y a plus besoin de conserver en mémoire les résultats de chaque appel récursif. Cela optimise
ainsi l’utilisation de la pile d’exécution.
La trace de FactorielleTerm(4) :
Remarque 97. La transformation d’un algorithme récursif en un algorithme récursif terminal n’est pas
évidente.
De plus, il est toujours possible de transformer un algorithme récursif terminal en une version explicite
(impérative), ce qui évite totalement l’emploi d’une pile d’une pile d’exécution.
En général, on évite souvent d’utiliser tout court des appels récursifs.
def binom(k,n) :
assert 0 <= k and k <= n
if k == 0 or k == n :
return 1
return binom(k,n-1)+binom(k-1,n-1)
4
Le nombre d’appels récursifs pour calculer binom(k,n) est assez important. Pour calculer 3 = 6 on a
besoin de 8 appels récursifs.
Ces algorithmes sont associés à des relations de récurrence qui font intervenir trois termes ou plus. Deux
autres exemples sont :
1. La suite de Fibonacci (et autres suites récurrentes linéaires d’ordre 2 avec un = aun−1 + bun−2 ).
2. Le tri fusion.
37
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
def A(m,n) :
assert m==int(m) and n==int(n) and m>=0 and n>=0
if m==0 :
return n+1
elif n==0 :
return A(m-1,1)
else :
return A(m-1,A(m,n-1))
Méme pour des petites valeurs de m et n, l’évaluation de A(m,n) excède les capacités de la machine à
cause du nombre d’appels récursifs nécessaires.
Par exemple, A(4,3).
Exercice 14.
1) Écrire sur Python le code de pair(n) et de impair(n) ;
2) Afficher la trace d’exécution pour calculer pair(6) ;
3) Afficher la trace d’exécution pour calculer impair(8).
Exercice 15. Décrire ce que fait cet algorithme récursif :
def triangle1(n) :
if n = = 0 :
return
else :
print("x"*n)
triangle1(n-1)
def triangle2(n) :
if n > 0 :
print("x"*n)
triangle2(n-1)
38
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
2. On ne peut déplacer qu’un anneau qui n’a pas d’anneau au dessus de lui ;
3. Les anneaux doivent toujours être empilés par diamètres décroissants.
Hanoï(a,b,c,n)
Si n = 1, déplacer l’anneau le plus haut en a, vers b
Sinon
appeler Hanoï(a,c,b,n-1)
déplacer l’anneau le plus haut en a, vers b
appeler Hanoï(c,b,a,n-1)
Cet algorithme fait deux appels récursifs chaque fois (sauf pour n = 1), c’est un algorithme à récursivité
multiple.
Sur Python, on obtient :
Tours de Hanoï
def Hanoi(n,source,auxiliaire,destination) :
if n==1 :
print("Déplacer l’anneau 1 depuis le poteau",source,
"vers le poteau",destination)
return
Hanoi(n-1,source,destination,auxiliaire)
print("Déplacer le disque",n,"depuis le poteau",source,
"vers le poteau",destination)
Hanoi(n-1,auxiliaire,source,destination)
39
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
.format(n, i, k))
hanoi(n-1, j, i, k)
40
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Graphes
Table des matières du chapitre
9.1 Introduction et vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
9.1.1 Graphe orienté, graphe non-orienté . . . . . . . . . . . . . . . . . . . . . . . . 41
9.1.2 Connexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
9.2 Matrice d’adjacence et liste d’adjacence d’un graphe . . . . . . . . . . . . 43
9.2.1 Matrice d’adjacence d’un graphe . . . . . . . . . . . . . . . . . . . . . . . . . 43
9.2.2 Liste d’adjacence d’un graphe . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
9.2.3 Pondération d’un graphe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
9.3 Parcours d’un graphe et plus court chemin . . . . . . . . . . . . . . . . . . 46
9.3.1 Algorithmes de parcours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
9.3.2 Parcours en profondeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
9.3.3 Parcours en largeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
9.4 Deux applications au parcours d’un graphe . . . . . . . . . . . . . . . . . . 50
9.4.1 Détection des parties connexes dans un graphe non-orienté . . . . . . . . . . 50
9.4.2 Détection de cycles dans un graphe orienté . . . . . . . . . . . . . . . . . . . 50
9.5 Parcours d’un graphe pondéré et plus court chemin . . . . . . . . . . . . 51
9.5.1 Algorithme de Dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
41
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Ce dessin de graphe ne représente pas un graphe orienté (on peut emprunter chaque pont dans un sens
ou dans l’autre, et certains sommets sont reliés plusieurs fois), mais un autre type de graphe (un graphe
non-orienté, avec un degré associé à chaque arrête).
Avec ce modèle, Euler a pu énoncer une proposition mathématique portant sur des graphes de la même
nature, et démontrer que dans ce cas particulier il n’y a pas de solution.
Remarque 107. Les graphes sont la formalisation d’un ensemble d’objets ayant des relations entre
eux : on gère donc, en plus de l’ensemble des objets, l’ensemble des relations qui peuvent lier deux
objets.
Ces objets peuvent être de n’importe quelle nature (nombres, fonctions, surfaces, personnes,. . .).
Exemples de problèmes modélisables :
1. Organisation d’un réseau informatique ou de transport (routier, aérien, maritime).
2. Connexions à un réseau électrique/d’eau/d’égoûts.
3. Liens dans un réseau social.
4. File d’attente (à l’OPT, à l’aéroport).
5. Parcours dans un labyrinthe.
Sur chacun de ces problèmes, on peut ensuite se poser beaucoup de questions (chemin le plus court/le
moins coûteux entre deux sommets, chemin le plus court qui passe par tous les sommets, nombre moyen
de connexions entre les sommets, distance moyenne entre deux sommets, sommets isolés ou ayant peu
de connexions au reste des sommets,. . .
Définition 108 (Degré dans un graphe orienté).
Soit G = (S, A) un graphe orienté. Soit s un sommet de G.
1. On note d+ (s) le degré sortant de s, comme le nombre d’arrêtes partant de s.
2. On note d− (s) le degré rentrant de s, comme le nombre d’arrêtes arrivant à s.
3. Les voisins de s sont les sommets s′ tels que (s, s′ ) ∈ A. Le sommet s a d+ (s) voisins.
Remarque 109. Pour G = (S, A) et s ∈ S un sommet de G, on peut tout à fait avoir (s, s) ∈ A.
C’est-à-dire l’existence d’une arrête qui relie s à s.
Cette arrête est particulière puisque son point de départ est aussi son point d’arrivée. On la représente
souvent sous forme de boucle (elle autorise de se déplacer dans le graphe G tout en restant au point s).
Définition 110 (Graphe non-orienté). Soit G = (S, A) un graphe.
Si l’ensemble A est symétrique ((s, s′ ) ∈ A ⇔ (s′ , s) ∈ A), on dit alors que G est un graphe non-orienté.
Dans un graphe non-orienté, si deux sommets s, s′ sont reliés par une arrête, on peut se déplacer de s
vers s′ ou de s′ vers s.
Exemple 111.
Cette définition est en fait identique à la précédente (d(s) = d+ (s)), seule la quantité d− (s) disparaît
(elle n’est plus nécessaire).
42
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Remarque 113. En informatique, on considère en général qu’un graphe non-orienté n’a pas de boucles
(pas d’arrêtes qui vont d’un sommet s vers lui-même).
Dans ce chapitre, le fait d’avoir des boucles ne pose pas de problèmes. Mais pour d’autres notions sur
les graphes, être sans boucles est le "cas général" et avoir des boucles est le "cas particulier" (les résultats
sont plus compliqués à écrire quand il y a des boucles).
X
Exercice 16. Pour les graphes non-orientés précédents, calculer Card(A) et d(s).
s∈S
9.1.2 Connexité
Définition 114. Soit G = (S, A) un graphe. Soient a, b ∈ S.
1. Un chemin de a vers b est une suite finie d’arrêtes de la forme
(a, a1 ), (a1 , a2 ), (a2 , a3 ), . . . , (ak−1 , b)
On le note aussi a → a1 → a2 → . . . → ak−1 → b ou c = (a, a1 , a2 , a3 , . . . , ak−1 , b).
2. On dit qu’un chemin de a vers b est élémentaire si tous les sommets parcourus sont distincts.
3. La longueur l(c) d’un chemin est le nombre d’arrêtes qui le composent.
4. Un circuit est un chemin de a vers a (quand a = b).
5. Un cycle est un circuit élémentaire : Il part de a, revient en a, et tous les sommets intermédiaires
sont deux à deux distincts. 1
Définition 115 (Connexité dans un graphe). Soit G = (S, A) un graphe. Soient a, b ∈ S.
On dit que a et b sont reliés s’il existe un chemin de a vers b.
On dit que a et b sont équivalents s’il existe un chemin de a vers b et un chemin de b vers a.
1. On appelle composante connexe du graphe l’ensemble C(s) de tous les points équivalents à s.
Le graphe G se retrouve alors partitionné (découpé) en un nombre fini de composantes connexes.
Certaines composantes connexes sont reliées entre elles (à sens unique), d’autres sont isolées.
2. Si le graphe G n’a qu’une seule composante connexe, on dit qu’il est connexe. 2
Exercice 17. Représenter toutes les composantes connexes de ces graphes. Sont-ils connexes ?
43
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
0 2 0 1 1 0 0
1 0 1 1 0
1 1 0 1 1
4
0 1 1 0 1
0 0 1 1 0
1 3
Remarque 119.
• Un graphe G est non-orienté si et seulement si sa matrice d’adjacence est symétrique.
• Les coefficients diagonaux de la matrice symétrique représentent les arrêtes qui relient un sommet
à lui-même.
• Dans un graphe non-orienté, les sommets ne sont pas forcément reliés à eux-mêmes.
• La matrice d’adjacence de G dépend de l’ordre de ses sommets. Si l’on change l’ordre des sommets
(des éléments de S), cela change l’écriture de la matrice d’adjacence. Cependant, cela conserve
toutes ses propriétés, donc ce n’est pas un problème.
Remarque 120. La matrice d’adjacence d’un graphe G contient toutes ses informations (nombre de
sommets et arrêtes).
On peut ainsi représenter le graphe G sur Python avec sa matrice d’adjacence M .
Avec ce point de vue, les manipulations sur G seront des manipulations sur la matrice M (produit de
matrices, échange de lignes/colonnes, extraire un coefficient).
Exercice 18. Soit G un graphe dont la matrice d’adjacence est M=[[1,0,0,1],[1,0,1,0],[1,0,1,1],[0,0,1,0]].
Représenter sur un dessin le graphe G (sommets, arrêtes). Est-il orienté ou non-orienté ?
Exemple 122.
0 2
1 3
44
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Maths Phys SI
On peut associer un numéro à chaque sommer et ainsi construire la liste d’adjacence du graphe. Mais,
si l’on veut conserver le nom initial des sommets, le dictionnaire d’adjacence est plus adapté.
Sur Python, cela donne :
Remarque 124. Sur Python, beaucoup d’opérations élémentaires pour les listes sont identiques à celles
des dictionnaires (ou des tableaux) : taille, données contenues, concaténer deux objets, lire une donnée,
boucle for sur toutes les donnée, boucle "tant que X est non-vide", . . .
D’autres opérations s’écrivent légèrement différemment (ajouter un nouvel élément, retirer un élément
spécifique, créer la liste/le dictionnaire vide).
Ainsi, les algorithmes utilisant une liste d’adjacence ou un dictionnaire d’adjacence sont quasiment
identiques.
Exemple 125. Soit G = (S, A) un graphe, codé sous forme de liste d’adjacence L. Voici un programme
Python qui prend en entrée L et qui retourne une liste contenant toutes les arrêtes de G (sous forme de
2-uplets).
def arcsListe(L):
arretes = []
for i in range len(L):
for j in L[1]:
arretes.append((i,j))
return(arretes)
Exercice 19. Pour G un graphe de matrice d’adjacence M , écrire une fonction arcsMatrice en Python
qui prend en entrée M et qui retourne une liste contenant toutes les arrêtes de G.
Définition 127. Sur Python, on représente un graphe pondéré G avec une matrice d’adjacence pondérée
M : mi,j = s(i,j) s’il y a une arrête reliant i et j, et mi,j = 0 sinon.
45
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Autrement dit, on remplace les 1 de la matrice d’adjacence par le poids de chaque arrête.
Exemple 128. On pose
M = [[2,3,0],[1,0,1],[-1,2,0]]
Remarque 129. On peut aussi représenter un graphe pondéré par une liste d’adjacence ou un diction-
naire d’adjacence. Il faudra cependant utiliser des listes de listes de 2-uplets (tuple) pour avoir à la fois
l’information sur les arrêtes et sur leurs poids.
Pour le graphe de l’exemple précédent, une liste d’adjacence est :
L=[[(0,2),(1,3)],[(0,1),(2,1)],[(0,-1),(1,2)]]
L[i] contient toutes les arrêtes pondérées partant de i. La première coordonnée de L[i][j], L[i][j][1], est
le numéro du sommet d’arrivée. La seconde, L[i][j][2], est le poids affecté à l’arrête.
Avec un dictionnaire d’adjacence, cela donne :
Adj = {"s1": [("s1",2),(("s",3)], "s2": [("s1",1),("s3",1)], "s3": [("s1",-1),("s2",2)], }
Il faut pour cela construire des algorithmes qui vont explorer le graphe, à la recherche d’un chemin entre
a et b.
Explorer le graphe veut dire que l’on ne se déplace que suivant les arrêtes, afin de trouver une suite
d’arrêtes (a, a1 ), (a1 , a2 ), . . . (an , b) qui relient a à b.
46
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Remarque 131.
• Cet algorithme va visiter, un à un, tous les sommets que l’on peut atteindre à partir de Si . Il se
termine bien car le graphe a un nombre fini de sommets. Si Si et Sc sont reliés, cela sera toujours
détecté.
• Si G a n sommets, la boucle "Tant que" sera exécutée au plus n fois.
• Il reste à décider de la façon de choisir qui sera le prochain sommet visité parmi les sommets en
gris (le premier dans la liste ? le dernier dans la liste ? au hasard ?).
• Cet algorithme ne fournit pas de chemin explicite qui relie Si à Sc .
Pour obtenir en réponse un chemin explicite qui relie Si à Sc , l’algorithme de parcours doit mémoriser
une information supplémentaire : Pour chaque sommet s′ que l’algorithme visite, il se souvenir du
sommet s déjà visité qui est relié à s′ (celui qui a permis d’aller vers s′ ).
Pour chaque sommet visité, on stocke l’information du "sommet précédent". On utilisera pour cela une
liste, que l’on appelle la liste des pères des sommets visités.
47
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Remarque 132. Pour choisir quel prochain sommet visiter dans un algorithme de parcours, il existe
deux grandes méthodes :
1. En profondeur : on choisit comme nouveau sommet le dernier sommet colorié en gris.
2. En largeur : on choisit comme nouveau sommet le premier sommet colorié en gris.
def profondeur(L,si,sc) :
N = [si] # On colore si en noir.
G = L[si] # On colore en gris les voisins de si.
while G != [] : # si G = [], exploration terminée
s = G.pop() # On prend le dernier élément de G, et on le retire de G.
N.append(s) # On colore s en noir.
for v in L[s] : # Pour tout v voisin de s
if v == sc :
N.append(v)
return N # Le sommet sc est atteint.
elif (v not in N) and (v not in G) :
G.append(v) # Si v est blanc, on le colorie en gris.
return False # Le sommet si n’est pas relié au sommet sc.
Remarque 134. La liste N des sommets visités semble former un chemin de si vers sc, mais ce n’est
pas toujours le cas (si l’algorithme explore une impasse, il doit revenir en arrière et prendre une autre
direction).
Pour obtenir un chemin de si vers sc, il faut ajouter une liste des pères P .
La liste des pères est une liste qui a autant d’éléments que G a de sommets.
Pour un sommet s, P [s] indique le "père" de s, ou None si s n’a pas encore été exploré.
Avec la liste des pères P , on créée le chemin C en remontant le trajet (partir de sc et revenir jusqu’à si
de père en père).
def profondeur(L,si,sc) :
P = [None]*len(L) # Création de la liste des pères.
C = [] # Création du chemin de si vers sc.
N = [si] # On colore si en noir.
G = L[si] # On colore en gris les voisins de si.
while G != [] : # si G = [], exploration terminée
s = G.pop() # On prend le dernier élément de G, et on le retire de G.
N.append(s) # On colore s en noir.
for v in L[s] : # Pour tout v voisin de s
if v == sc : # Le sommet sc est atteint.
N.append(v)
C=[sc]+C # construction du chemin C en revenant en arrière.
while s != si :
s = P[s] # On remonte au père du sommet s.
C = [s]+C # On ajoute le père au chemin C, jusqu’à revenir au sommet si.
return C # On renvoie le chemin C.
48
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
def profondeur(L,si,sc) :
N = [si] # On colore si en noir.
G = L[si] # On colore en gris les voisins de si.
while G != [] : # si G = [], exploration terminée
s = G[0] # On prend s le premier élément de G.
G.remove(s) # On retire le premier élément de G.
N.append(s) # On colore s en noir.
for v in L[s] : # Pour tout v voisin de s
if v == sc :
N.append(v)
return N # Le sommet sc est atteint.
elif (v not in N) and (v not in G) :
G.append(v) # Si v est blanc, on le colorie en gris.
return False # Le sommet si n’est pas relié au sommet sc.
Voyons maintenant un second algorithme utilisant une file, qui fournit un chemin entre si et sc (qui
s’avère être un chemin de longueur minimale).
Pour utiliser une file (queue) sur Python, on peut importer le module queue (import queue as *).
Cependant, on prefère importer les double-ended queues (files à double-extrémité) qui fournissent un
objet qui peut servir à la fois de pile et de file (from collections import deque). L’ajout et le retrait
d’un élément sur un deque a une complexité en O(1) dans les deux sens.
49
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Dans le pire des cas, il faut parcourir un graphe G en entier pour trouver un chemin entre si et sc (si
le graphe est en forme de ligne droite par exemple). La complexité dans le pire des cas d’un algorithme
de parcours en longueur/largeur est donc en O(n), où n est le nombre de sommets du graphe.
50
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Toute la théorie derrière la simplification d’un graphe G en transformant chaque cycle en un seul sommet
(construire un second graphe qui n’a plus de cycles et qui "correspond" à G) est hors programme.
51
Lycée du Diadème - Te Tara o Mai’ao PTSI, Année 2023-2024
Remarque 141. Si l’on retire la condition S == Sc , l’algorithme de Dijkstra calculera les distances
minimales entre Si et n’importe quel point de G relié à Si (ce sont les poids des points coloriés en noir).
La liste des pères fournit quand à elle un chemin de poids minimal entre Si et n’importe quel point de
G (on part du point final et on remonte à Si par les pères). Le chemin entre Si et Sc de poids minimal
n’est qu’une fraction des informations que calcule cet algorithme.
Si le graphe G a n sommets, et si v est le maximum du nombre de voisins pour chaque sommet,
l’algorithme de Dijkstra a une complexité en O(vn ln(n)).
Il s’agit d’un algorithme de parcours en profondeur. Pour chaque sommet gris on calcule ∥s − Sc ∥, et le
prochain sommet gris exploré est celui dont la distance à Sc est la plus petite. On regarde ici la distance
entre des points du plan/de l’espace (la distance "à vol d’oiseau", rapide à calculer).
Cet algorithme fournit rapidement un chemin reliant Si et Sc . Ce chemin n’est pas celui de distance
minimale, mais il en est proche.
Algorithme A∗
L’algorithme de Dijkstra détermine le nouveau sommet gris S à explorer en prenant celui dont la dis-
tance à Si (distance selon les arrêtes) est la plus petite. Il fournit toujours la meilleure solution mais
stocke et renouvelle beaucoup d’informations.
L’algorithme Best-First détermine le nouveau point gris S à explorer en prenant celui dont la distance
à Sc (distance dans le plan/l’espace) est la plus petite. Il ne fournit pas la meilleure solution, mais il
demande moins d’étapes et ne renouvelle pas d’informations sur les points gris.
Ces deux approches peuvent être mélangées : c’est l’algorithme A∗ . Cet algorithme est un algorithme
de parcours en profondeur, de structure identique à celle de Dijkstra.
Pour chaque sommet S on stocke deux informations :
• La distance du sommet Si à S selon les poids des arrêtes, d(S).
Si le sommet est blanc on a d(S) = +∞.
Si le sommet est gris, cette distance peut être mise à jour si l’on trouve S ′ un parent de S tel
que d(S ′ ) + l(S ′ , S) < d(S).
Si le sommet est noir, cette distance est optimale.
• La distance euclidienne entre S et Sc : d(S, Sc ) = ∥S − Sc ∥.
Si le sommet est blanc on pose d(S, Sc ) = +∞.
Si le sommet est gris, on calcule ∥S − Sc ∥.
Avec ces deux quantités, on détermine le nouveau sommet gris S à explorer en prenant celui dont la
quantité d(S) + αd(S, Sc ) est la plus petite.
Le paramètre α ≥ 0 sert de pondération entre Dijkstra et Best-First.
Ce type de mélange est souvent utilisé pour combiner les propriétés intéressantes de plusieurs algo-
rithmes traitant d’un même type de problème. Le résultat est bien plus efficace/qualitatif que les deux
algorithmes pris séparément.
52